Squashed 'third_party/SPIRV-Tools/' changes from f37547c73..339d4475c

339d4475c Improve decoration validation (#4490)
1589720e1 spirv-opt: create OpDecorate for OpMemberDecorate in desc-sroa (#4617)
6b073f899 Run the wasm build on push and pull_request (#4614)
66ef7cb68 Remove publish-to-npm step for Wasm build (#4610)
d645ea270 Update CHANGES, README for WebAssembly build (#4609)
0d0013002 Add Wasm build (#3752)
1082de6bb Handle overflowing id in merge return (#4606)
97d449560 Avoid bitwise and in boolean expression (#4603)
e3c709810 In generated cmake config file for SPIRV-Tools only access cmake target, if present (#4590)
e3a373f2b Make cxx exceptions controllable  (#4591)
c194bb2a7 spirv-val Update LocalSizeId VUID (#4602)
6c7885dbd Change branch handling in ADCE to fix errors (#4596)
bd5bf754b Fix Linker generator ID (#4601)
791f5b463 Only validate workgroup layout for explicit workgroup memory (#4542)
2feb7074d Avoid confusing short-circuiting (#3404)
7c5b17d37 Update passes to handle function declarations (#4599)
b2ba019bf Delete decorations before replaces uses in dead branch elim (#4598)
3291b6951 Do not fold snegate feeding sdiv. (#4600)
d997c83b1 Add spirv-opt pass to replace descriptor accesses based on variable indices (#4574)
d78c1c4cd Make IsLocalVar in ADCE work at any time. (NFC) (#4595)
8c91f14a9 Add libFuzzer target for spirv-fuzz (#4434)
17a5bacfa Handle missing execution modes for limitation check (#4594)
7326b494d opt: set upper bits of spec constant according to spec (#4589)
f3fbd98ff opt/spec_constants: fix bit pattern width checks. (#4588)
0f3bc1d9b Fix i386 build issues related to random generation (#4586)
001604bd4 Generate constants directly in CreateDebugInlinedAt (#4572)
7a7a69037 README: Remove appveyor build badge. (#4584)
4578db3c4 README: Update tested versions of compilers (#4579)
387cae472 Opt passes should apply to the exported functions (#4554)
35fd0e17d Consider 0xffffffff offset as missing (#4564)
06ebc4806 Account for strided components in arrays (#4575)
10343e53e Remove Appveyor CI, update README (#4578)
3e6a85303 Mark DebugInfoNone as live in ADCE when DebugInfo present (#4568)
e6e77dbdf Enable OpConstFunctionPointerINTEL outside function (#4576)
5ed637caa Fix output of SPIR-V version in diagnostic (#4562)
d31218c56 Use max enum instead of static array of legal values (#4571)
0292d6b16 Update SPIRV-Headers (#4573)
6dd73728e Fix merge-block assertions with debugInfo (#4563)
e4349dd8f Fix CI failure (#4570)
b46995741 Avoid bugprone-move-forwarding-reference warning in Clang (#4560)
0c4deebc9 Include a maximum value for spv_target_env (#4559)
63a391232 Fix ConstantManager to not run AnalyzeInstDefUse if DefUse not valid (#4557)
2d12367ce Stop consuming input in fuzzers to select target environment (#4544)
eeb973f50 More ADCE refactoring (#4548)
9529d3c2c Avoid implicit fallthrough, by duplicating code (#4556)
24476c2e3 spirv-opt: Don't eliminate dead members from StructuredBuffer (#4553)
3234daada Do not assume there are execution modes (#4555)
ba4b390c3 Suppress protobuf warning (#4551)
c3adcb034 Adce refactor (NFC) (#4547)
19dc86c48 Handle NonSemantic.Shader Debug[No]Line (#4530)
f125452cf Fix inst_buff_addr_check to handle struct loads (#4489)
134d763f4 GN: Fix build for debuginfo codegen. (#4536)
1d48824ba Update SPIRV-Headers (#4543)
a6c5056db Fix checks for offset in nested structs (#4531)
0f4508752 Fix UBSan error in spirv-dis fuzzer (#4517)
59f51bb4f Fix extract with out-of-bounds index (#4529)
1454c95d1 spirv-opt: Switch from Vulkan.DebugInfo to Shader.DebugInfo (#4493)
4ac8e5e54 Add preserve_interface mode to aggressive_dead_code_elim (#4520)
9e65f054d spirv-fuzz: Account for differing signedness in WrapVectorSynonym (#4414)
36ff13534 spirv-opt: Avoid integer overflow during constant folding (#4511)
cb6c66917 Roll external/googletest/ b7d472f12..955c7f837 (111 commits) (#4521)
846b032b5 Fix infinite loop in validation (#4523)
bf463fe37 Fix UBSan kokoro config (#4522)
5efeaad30 Fix bad order of checks in atomic validation (#4524)
8865b2029 Handle out-of-bounds accesses in VDCE (#4518)
4f4f76037 Change validator boolean tests to avoid asserts (#4503)
912460e46 Fix infinite loop in GetBlockDepth (#4519)
013b1f3d6 Fix validation message for cooperative matrix column type (#4502)
2a938fcfa Add UBSan kokoro configuration (#4512)
c16224c68 Add some missing switch validation (#4507)
92868b8f3 spirv-val: Fix ubsan error (#4505)
4db6b8dcc Remove environment features that are never used (#4491)
7e860e383 fix parsing of bad binary exponents in hex floats (#4501)
789de0dc4 Adjust build for fuzzer targets to support OSS-Fuzz (#4498)
0c09258e0 Set threshold for reduce-load-size pass (#4499)
bd3a271ce Handle exported functions in ADCE (#4495)
702e6af38 Only add `-DSPIRV_CHECK_CONTEXT` for Debug builds (#4496)
b8fce5f9e spirv-lint: Add lint based on divergence analysis (#4488)
ee3077365 Add a feature for allowing LocalSizeId (#4492)
2a5cc342f Start SPIRV-Tools v2021.4
1fbed83c8 Finalize SPIRV-Tools v2021.3
3d4246b4a Update CHANGES
a1d5d67ae spirv-val: Validate vulkan debug info similarly to opencl debug info (#4466)
937227c76 Add divergence analysis to linter (#4465)
d699296b4 spirv-val: Fix WorkgroupSize VUID 04425 (#4482)
1ad8b7135 spirv-lint: add basic CLI argument handling (#4478)
a9ce5e07c Fix matrix stride validation (#4468)
e172833bd Don't double count variables for location validation (#4474)
926ff6d1b GN: Suppress unreachable code warnings. (#4476)
57e1d8ebe Add spirv-opt convert-to-sampled-image pass (#4340)
b2e36b67e Disallow loading a runtime-sized array (#4473)
2c829c415 Fix early-out for Clamp constant folding (#4461)
869a550d2 Don't fold unsigned divides of an constant and a negation (#4457)
881001070 fix SIGSEGV when reading from a non-existant file (#4453)
00b106e76 Limit location validation (#4467)
54524ffa6 Update SPIRV-Headers (#4463)
de69f32e8 spirv-opt: Add handling of vulkan debug info to DebugInfoManager (#4423)
c4c6f2ba5 spirv-opt: Add dataflow analysis framework (#4402)
f9d03bb40 Remove PCH from source/lint/CMakeLists.txt (#4459)
706dc27a6 Add new target for spirv-lint (#4446)
3510a14cf Add a section releases to the README (#4444)
175ecd49e Fix array layout validation slowdown (#4449)
07f130235 spirv-fuzz: Support AtomicStore (#4440)
366d1be5e fuzzers: Disable suggest-destructor-override warning (#4439)
3ab6fb9c0 Add CMake rules for libFuzzer targets (#4445)
0065c5672 spirv-fuzz: support AtomicLoad (#4330)
affe280c2 Add GraphicsFuzz shaders to fuzzer corpus (#4429)
c5bda7ae5 Fuzzer: Default the new constructor parameter (#4438)
5737dbb06 Fix validator crash (#4418)
17bf44376 spirv-fuzz: Add minimal SPIR-V example to test shaders (#4415)
c6422cff3 spirv-opt: Rename ControlDependenceAnalysis::DoesBlockExist to HasBlock (#4412)
983ee2313 spirv-opt: Add specific handling of vulkan debug info differences (#4398)
9c4481419 spirv-fuzz: Allow inapplicable transformations to be ignored (#4407)
c9e094cc4 spirv-fuzz: Quit fuzzer pass when no types are available (#4409)
4bcd13ff1 spirv-opt: Add more tests to control dependence  (#4410)
7dadcf9c7 Add control dependence analysis to opt (#4380)
11cd875ed spirv-fuzz: Use reference in CanMakeSynonymOf (#4401)
4376a10c1 Fix public deps on generated headers (#4386)
b2db20a7e BUILD.gn: introduce finer grained internal targets for Tint (#4399)
63238d4f2 Initialize context in `opt::Instruction`'s move constructor (#4397)
183fb9fe4 spirv-fuzz: Fix problem with instruction context (#4394)
94bcae134 spirv-fuzz: Avoid out-of-bounds access (#4395)
cc3fe2b67 spirv-fuzz: Fix vector wrapping fuzzer pass (#4392)
2419f3be8 spirv-fuzz: Tighten checks on null and undef pointers (#4367)
2a7a561c0 Fix local size hint id tests (#4400)
d9f892578 spirv-opt: Where possible make code agnostic of opencl/vulkan debuginfo (#4385)
033768c24 spirv-fuzz: TransformationWrapVectorSynonym that rewrites scalar operations using vectors (#4376)
f084bcfe2 spirv-fuzz: Support atomic operations opcode (#4348)
3a68a7274 CMake: add ENABLE_RTTI option (#4382)
8966cc2b2 Add common enum for debug info instructions from either opencl or vulkan (#4377)
5427d5cf0 Don't mention VS2013 in PR review instructions (#4384)
7320b9acd Explain how to run tests with CMake and Bazel (#4383)
e0937d7fd spirv-fuzz: Don't replace memory semantics / scope operands (#4349)
2685c9a68 Add missing fuzzer header dependency. (#4381)
640b17b5f Fix -Wunreachable-code-aggressive. (#4358)
4baf3affe spirv-opt: support SPV_EXT_shader_image_int64 (#4379)
feb05446b Fix BUILD.gn (#4378)
2299b710d spirv-fuzz: support building using gn (#4365)
d432bebb1 Fix vendor table build in BUILD.gn for nonsemantic.vulkan.debuginfo.100 (#4375)
3b6abf41c Add non-semantic vulkan extended instruction set (#4362)
c26baf4c9 Update SPIRV-Headers deps (#4369)
9ce7a2fb6 spirv-reduce: Eliminate skeletal structured control flow construct (#4360)
4d2832e3c spirv-fuzz: Check updated analyses in transformation tests (#4266)
776336052 spirv-fuzz: Added tests for signedness analysis (#4361)
a95bc460f Add remove_unused_interface_variable_pass.* to BUILD.gn (#4363)
c67f13208 add tests for SPV_KHR_bit_instructions (#4350)
06f114d48 spirv-fuzz: Avoid out of bounds access (#4355)
74e8105eb Enabled tvOS platform (#4329)
f9893c454 spirv-opt: A pass to removed unused input on OpEntryPoint instructions. (#4275)
8442a1812 Bump glob-parent from 5.0.0 to 5.1.2. (#4353)
eeff9af1e fix strncpy bound error (#4331)
b8587c984 spirv-reduce: Allow merging unreachable blocks (#4303)
4fcdc5894 Add IsReachable function to IRContext (#4323)
237173a07 spirv-reduce: Cleanup a few things (#4352)
8cc8b6562 spirv-fuzz: Add illustrative tests for new issues (#4347)
3a02d1126 Add validation for SPV_EXT_shader_atomic_float16_add (#4325)
e065c482c Initial support for SPV_KHR_integer_dot_product (#4327)
e992c96c8 fix symbol exports check, for Android build cases (#4342)
0c21e5092 Start SPIRV-Tools v2021.3
e198c6a78 Finalize SPIRV-Tools v2021.2
f8eafd4d8 spirv-val: Label VUID 04780 (#4334)
e84bcb251 spirv-val: Add GLCompute to VUID 04644 message (#4333)
5dd2f7691 Update CHANGES file for upcoming release.
bcef91374 Update SPIRV-Headers deps (#4328)
4d22f58a8 Support SPV_KHR_subgroup_uniform_control_flow (#4318)
ecdd9a3e6 spirv-val: Vulkan Storage Class for Execution Model (#4212)
edc3a2478 Add SPV_KHR_vulkan_memory_model to aggressive_dead_code_elim (#4320)
bbc660eda Support Intel extensions for fixed point and hls-float (#4321)
9dbca316a spirv-fuzz: Improve TransformationAddBitInstructionSynonym to check integer signedness (#4312)
c1a75bfab spirv-fuzz: Support bitwise or and xor in TransformationAddBitInstructionSynonym (#4310)
d07505c76 Update lodash (#4317)
fb02131cb No error report for variable image offset when before-legal-hlsl is on (#4316)
87a286797 Add kInstErrorMax to instrument.hpp (#4315)
c05f74415 Spirv-fuzz: Add tests for MaybeGet* functions in fuzzerutil (#4284)
6d5c5677e Remove VS2013 kokoro build (#4314)
26cdce984 spirv-fuzz: add tests for full coverage of TransformationAccessChain (#4304)
c853a9114 spirv-fuzz: Added test to increase coverage of the add_parameter transformation (#4305)
126a826d3 Spirv-fuzz: Achieve coverage of TransformationAddDeadBlock test  (#4306)
8b3dc6bbe Check that valid bitcasted constant was returned (#4311)
0861a8fa2 spirv-fuzz: Fix OutlineFunction in presence of unreachable blocks (#4308)
9646c733e spirv-fuzz: Fix def-use update in PermutePhiOperands (#4309)
ec1bc3e2e spirv-fuzz: Added extra tests for AddTypeFloat and AddTypeInt transformations (#4292)
94f570d7a spirv-fuzz: Increase test coverage of TransformationCompositeConstruct (#4301)
00ce2bb47 spirv-fuzz: Enhancing permute function variables and its testing (#4295)
e2ac64bdf spirv-fuzz: Move ApplyTransformation to .cpp file (#4258)
91931ffcd spirv-fuzz: Enhance test to improve lines covered (#4289)
0afe1f2b3 spirv-fuzz: Cover protobuf message creation in tests (#4285)
f0d110e30 Invalidate analyses (#4260)
18d45142e Fix crash when optimizing shaders with DebugPrintf (#4280)
010cd289d Fix continue construct for single block loops (#4277)
f6b59599a spirv-fuzz: Respect control flow rules when merging returns (#4279)
de1cae069 val test: Update capability dependency (#4268)
1020e394c spirv-fuzz: Fix underflow problem in `fuzzer_pass_swap_two_functions` (#4253)
8ec9f456e Fix export symbol test. (#4254)
f30465d2b build: Fix Android build (#4157)
f8c78cc53 Updates to the vscode SPIR-V extension (#4246)
f82f5af5e Invalidate DefUse analysis (#4255)
089d716d2 Fix dangling phi bug from loop-unroll (#4239)
07ec4f83c Support folding OpBitcast with numeric constants (#4247)
6cdf07d2b spirv-fuzz: Swap positions of two functions in a module (#4236)
1b8341b8b Fix warning in python script. (#4251)
9f23457ee GraphicsRobustAccessPass: Set module_status_.modified (#4167)
8fe39ad58 spirv-fuzz: Permute the order of variables at function scope issue (#4248)
22b82872b Start SPIRV-Tools v2021.2
c2d5375fa Finalize SPIRV-Tools v2021.1
dc72924cb Update CHANGES
693d564db spriv-val: Fix clang-format bug for VUID string (#4238)
a02a9205f spirv-fuzz: Accept limitations in AddFunction (#4226)
ae6a1e1d2 Fix UWP build (#4235)
6210375e1 Fix clang-format-diff.py URL (#4233)
8da800c4c spirv-reduce: Remove redundant r-value references (#4232)
212895d4c Typo fix (#4225)
48007a5c7 Add interpolate legalization pass (#4220)
61e256c9c spirv-fuzz: Efficiency improvements to fuzzer pass (#4188)
2ee21fbde spirv-fuzz: Avoid invalidating analyses when splitting blocks (#4218)
22eb528f1 spirv-fuzz: Do not add too many dead blocks (#4217)
d0c73fcee spirv-fuzz: Optimize transformations (#4216)
f22793015 spirv-fuzz: Fix comments #4215
ecc840d30 Add validation for SPV_EXT_shader_atomic_float_min_max (#4105)
d20c9c2cf Make spirv-tools-build-version a common dependency (#4210)
bed84792f spirv-fuzz: Call by value and move in transformations (#4208)
c0833ce62 spirv-fuzz: Remove destructors from FuzzerPass subclasses (#4209)
3d3951796 spirv-fuzz: Improve transformation test oracles (#4207)
edb8399b0 spirv-fuzz: Add WGSL compatibility flag to context (#4193)
6382cbb49 spirv-fuzz: Avoid invalidating analyses in various transformations (#4205)
657889978 spirv-fuzz: Manage available instructions efficiently (#4177)
75d7c14cf spirv-fuzz: Remove AddType methods from fuzzerutil (#4204)
f2a19b015 spirv-val: Refactor of atomic pass (#4200)
8f421ced3 spirv-val: Label VUID 04643 (#4202)
2b0d16a05 spirv-val: Label VUID 04667 (#4201)
a732e4c03 spirv-fuzz: Apply fuzzer pass before checking exit conditions (#4199)
a611be778 spirv-val: Fix Int64Atomics check (#4192)
4f498774d Roll deps (#4185)
c040bd3ae spirv-val: Add Vulkan Execution Scope checks (#4183)
8866fd7ae spirv-fuzz: Locate instructions more efficiently (#4189)
db2a70646 spirv-fuzz: Make adding equation instructions more efficient (#4190)
e8ab7101f spirv-fuzz: Make PermutePhiOperands more efficient (#4191)
79ab273f9 Accept OpImageTexelPointer user in scalar-replacement (#4187)
042eff73f spirv-val: Add Vulkan Invocation Sematics check (#4182)
03f23106c spirv-val: Label VUID 04634 (#4181)
4100477e7 Support SPV_KHR_linkonce_odr, SPV_KHR_expect_assume (#4161)
478754c00 spirv-fuzz: Avoid invalidating analyses (#4176)
777990758 spirv-fuzz: Add buggy test (#4180)
9e93b165c Remove usage of std::iterator. (#4171)
5d8c40399 BUILD.gn: fix typo for 'cflags' (#4169)
77eb2b608 Suppress warning (#4168)
f7cf3ec2a spirv-fuzz: Avoid unnecessary dependency (#4165)
1746ed39d Add `void` in function declaration to keep some compilers happy (#4160)
4a59fd476 Fix -Wextra-semi-stmt -Wsuggest-destructor-override -Wdeprecated-copy-dtor (#4164)
c6da5e343 spirv-val: Vulkan 64-bit OpAtomicStore check (#4163)
e6a9f4e43 spirv-fuzz: Fix the bug in TransformationReplaceBranchFromDeadBlockWithExit (#4140)
7d514cf1c spirv-fuzz: Fix PartialCount (#4159)
f7043c0de spirv-fuzz: Handle Vulkan SPIR-V versions (#4156)
43cfa9bc1 spirv-fuzz: Add persistent state to the fuzzer (#4137)
939bc0260 Require an OpSelectionMerge before an OpSwitch (#4154)
0bd920eb9 Use standard function to get stdin to binary mode. (#4141)
d28186db9 Fixes for the vscode language server extension (#4150)
ef3290bbe spirv-opt: Don't call GenerateCopy for mismatched image types (#4126)
1b35745ad Start SPIRV-Tools v2021.0
07383c65d Finalize SPIRV-Tools v2020.7
05cda81ab Update CHANGES
c79edd260 Generate differentiated error codes for buffer oob checking (#4144)
cfa1dadb1 Update a few virtuals to overrides. (#4143)
f0c96f40c spriv-val: Vulkan image gather constant component (#4133)
3ad7e5fcc spirv-val: Fix/Label UniformConstant VUID (#4134)
f11f74348 spirv-val: Add Vulkan Invariant Decoration VUID (#4132)
c91a25af1 spirv-val: label tests for VUID 04657 (#4119)
d61a7d110 spirv-val: Add Vulkan PSB64 convert VUID (#4122)
297723d75 Mark module as modified if convert-to-half removes decorations. (#4127)
e8bd26e1f Set correct scope and line info for DebugValue (#4125)
b812fd634 Validate SPV_KHR_workgroup_memory_explicit_layout (#4128)
cc81f53d3 Validate VK_KHR_zero_initialize_workgroup_memory (#4124)
d71ac38b8 spirv-val: Add Vulkan image gather offset VUID (#4118)
a4f97da40 Add cmake to windows path for kokoro (#4129)
a61600c76 spirv-val: Label Vulkan atomic semantics VUIDs (#4120)
819117cd4 spirv-val: Label VUID 04662 (#4123)
89ad2272b spirv-val: Label VUID 04683 (#4121)
968659aee Remove obsolete GN config (#4110)

git-subtree-dir: third_party/SPIRV-Tools
git-subtree-split: 339d4475c1a806c187c57678af26733575d1cecd
diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 0a4cca0..0000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,90 +0,0 @@
-# Windows Build Configuration for AppVeyor
-# http://www.appveyor.com/docs/appveyor-yml
-
-# version format
-version: "{build}"
-
-# The most recent compiler gives the most interesting new results.
-# Put it first so we get its feedback first.
-os:
-  - Visual Studio 2017
-  #- Visual Studio 2013
-
-platform:
-  - x64
-
-configuration:
-  - Debug
-  #- Release
-
-branches:
-  only:
-    - master
-
-# Travis advances the master-tot tag to current top of the tree after
-# each push into the master branch, because it relies on that tag to
-# upload build artifacts to the master-tot release. This will cause
-# double testing for each push on Appveyor: one for the push, one for
-# the tag advance. Disable testing tags.
-skip_tags: true
-
-clone_depth: 1
-
-matrix:
-  fast_finish: true # Show final status immediately if a test fails.
-  #exclude:
-  #  - os: Visual Studio 2013
-  #    configuration: Debug
-
-# scripts that run after cloning repository
-install:
-  # Install ninja
-  - 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;C:\Python36;%PATH%
-
-before_build:
-  - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
-  - git clone https://github.com/google/googletest.git external/googletest
-  - cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd ..
-  - git clone --depth=1 https://github.com/google/effcee.git external/effcee
-  - git clone --depth=1 https://github.com/google/re2.git external/re2
-  # Set path and environment variables for the current Visual Studio version
-  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" (call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86_amd64)
-  - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64)
-
-build:
-  parallel: true  # enable MSBuild parallel builds
-  verbosity: minimal
-
-build_script:
-  - mkdir build && cd build
-  - 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 310
-
-after_test:
-  # Zip build artifacts for uploading and deploying
-  - cd install
-  - 7z a SPIRV-Tools-master-windows-"%PLATFORM%"-"%CONFIGURATION%".zip *\*
-
-artifacts:
-  - path: build\install\*.zip
-    name: artifacts-zip
-
-deploy:
-  - provider: GitHub
-    auth_token:
-      secure: TMfcScKzzFIm1YgeV/PwCRXFDCw8Xm0wY2Vb2FU6WKlbzb5eUITTpr6I5vHPnAxS
-    release: master-tot
-    description: "Continuous build of the latest master branch by Appveyor and Travis CI"
-    artifact: artifacts-zip
-    draft: false
-    prerelease: false
-    force_update: true
-    on:
-      branch: master
-      APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml
new file mode 100644
index 0000000..d9a9c5c
--- /dev/null
+++ b/.github/workflows/wasm.yml
@@ -0,0 +1,14 @@
+name: Wasm Build
+
+on: [ push, pull_request ]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Build web
+        run: docker-compose up
+      - name: Run tests
+        run: node test/wasm/test.js
diff --git a/Android.mk b/Android.mk
index 1000f42..bc748e5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -88,8 +88,11 @@
 		source/opt/composite.cpp \
 		source/opt/const_folding_rules.cpp \
 		source/opt/constants.cpp \
+		source/opt/control_dependence.cpp \
+		source/opt/convert_to_sampled_image_pass.cpp \
 		source/opt/convert_to_half_pass.cpp \
 		source/opt/copy_prop_arrays.cpp \
+		source/opt/dataflow.cpp \
 		source/opt/dead_branch_elim_pass.cpp \
 		source/opt/dead_insert_elim_pass.cpp \
 		source/opt/dead_variable_elimination.cpp \
@@ -97,6 +100,7 @@
 		source/opt/debug_info_manager.cpp \
 		source/opt/def_use_manager.cpp \
 		source/opt/desc_sroa.cpp \
+		source/opt/desc_sroa_util.cpp \
 		source/opt/dominator_analysis.cpp \
 		source/opt/dominator_tree.cpp \
 		source/opt/eliminate_dead_constant_pass.cpp \
@@ -122,6 +126,7 @@
 		source/opt/instruction.cpp \
 		source/opt/instruction_list.cpp \
 		source/opt/instrument_pass.cpp \
+		source/opt/interp_fixup_pass.cpp \
 		source/opt/ir_context.cpp \
 		source/opt/ir_loader.cpp \
 		source/opt/licm_pass.cpp \
@@ -152,6 +157,8 @@
 		source/opt/register_pressure.cpp \
 		source/opt/relax_float_ops_pass.cpp \
 		source/opt/remove_duplicates_pass.cpp \
+		source/opt/remove_unused_interface_variables_pass.cpp \
+		source/opt/replace_desc_array_access_using_var_index.cpp \
 		source/opt/replace_invalid_opc.cpp \
 		source/opt/scalar_analysis.cpp \
 		source/opt/scalar_analysis_simplification.cpp \
@@ -179,6 +186,7 @@
 SPV_OPENCL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.opencl.std.100.grammar.json
 SPV_DEBUGINFO_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.debuginfo.grammar.json
 SPV_CLDEBUGINFO100_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json
+SPV_VKDEBUGINFO100_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json
 
 define gen_spvtools_grammar_tables
 $(call generate-file-dir,$(1)/core.insts-unified1.inc)
@@ -210,6 +218,7 @@
 	$(1)/opencl.std.insts.inc \
 	$(1)/debuginfo.insts.inc \
 	$(1)/opencl.debuginfo.100.insts.inc \
+	$(1)/nonsemantic.shader.debuginfo.100.insts.inc \
 	$(1)/spv-amd-gcn-shader.insts.inc \
 	$(1)/spv-amd-shader-ballot.insts.inc \
 	$(1)/spv-amd-shader-explicit-vertex-parameter.insts.inc \
@@ -239,6 +248,7 @@
 # We generate language-specific headers for DebugInfo and OpenCL.DebugInfo.100
 $(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),DebugInfo,$(SPV_DEBUGINFO_GRAMMAR)))
 $(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),OpenCLDebugInfo100,$(SPV_CLDEBUGINFO100_GRAMMAR)))
+$(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),NonSemanticShaderDebugInfo100,$(SPV_VKDEBUGINFO100_GRAMMAR)))
 
 
 define gen_spvtools_vendor_tables
@@ -256,6 +266,7 @@
 # Vendor and debug extended instruction sets, with grammars from SPIRV-Tools source tree.
 $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),debuginfo,""))
 $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),opencl.debuginfo.100,"CLDEBUG100_"))
+$(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),nonsemantic.shader.debuginfo.100,"SHDEBUG100_"))
 $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-gcn-shader,""))
 $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-ballot,""))
 $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-explicit-vertex-parameter,""))
diff --git a/BUILD.bazel b/BUILD.bazel
index 52290cf..b2031de 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -3,6 +3,7 @@
     "COMMON_COPTS",
     "DEBUGINFO_GRAMMAR_JSON_FILE",
     "CLDEBUGINFO100_GRAMMAR_JSON_FILE",
+    "SHDEBUGINFO100_GRAMMAR_JSON_FILE",
     "TEST_COPTS",
     "base_test",
     "generate_core_tables",
@@ -12,6 +13,7 @@
     "generate_opencl_tables",
     "generate_vendor_tables",
     "link_test",
+    "lint_test",
     "opt_test",
     "reduce_test",
     "util_test",
@@ -59,12 +61,16 @@
 
 generate_vendor_tables("opencl.debuginfo.100", "CLDEBUG100_")
 
+generate_vendor_tables("nonsemantic.shader.debuginfo.100", "SHDEBUG100_")
+
 generate_vendor_tables("nonsemantic.clspvreflection")
 
 generate_extinst_lang_headers("DebugInfo", DEBUGINFO_GRAMMAR_JSON_FILE)
 
 generate_extinst_lang_headers("OpenCLDebugInfo100", CLDEBUGINFO100_GRAMMAR_JSON_FILE)
 
+generate_extinst_lang_headers("NonSemanticShaderDebugInfo100", SHDEBUGINFO100_GRAMMAR_JSON_FILE)
+
 py_binary(
     name = "generate_registry_tables",
     srcs = ["utils/generate_registry_tables.py"],
@@ -101,12 +107,14 @@
         ":gen_enum_string_mapping",
         ":gen_extinst_lang_headers_DebugInfo",
         ":gen_extinst_lang_headers_OpenCLDebugInfo100",
+        ":gen_extinst_lang_headers_NonSemanticShaderDebugInfo100",
         ":gen_glsl_tables_unified1",
         ":gen_opencl_tables_unified1",
         ":gen_registry_tables",
         ":gen_vendor_tables_debuginfo",
         ":gen_vendor_tables_nonsemantic_clspvreflection",
         ":gen_vendor_tables_opencl_debuginfo_100",
+        ":gen_vendor_tables_nonsemantic_shader_debuginfo_100",
         ":gen_vendor_tables_spv_amd_gcn_shader",
         ":gen_vendor_tables_spv_amd_shader_ballot",
         ":gen_vendor_tables_spv_amd_shader_explicit_vertex_parameter",
@@ -226,6 +234,19 @@
 )
 
 cc_library(
+    name = "spirv_tools_lint",
+    srcs = glob(["source/lint/*.cpp", "source/lint/*.h"]),
+    hdrs = ["include/spirv-tools/linter.hpp"],
+    copts = COMMON_COPTS,
+    linkstatic = 1,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":spirv_tools",
+        ":spirv_tools_opt",
+    ],
+)
+
+cc_library(
     name = "tools_util",
     srcs = glob(["tools/util/*.cpp"]),
     hdrs = glob(["tools/util/*.h"]),
@@ -323,6 +344,21 @@
 )
 
 cc_binary(
+    name = "spirv-lint",
+    srcs = [
+        "tools/io.h",
+        "tools/lint/lint.cpp",
+    ],
+    copts = COMMON_COPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":spirv_tools",
+        ":spirv_tools_lint",
+        ":tools_util",
+    ],
+)
+
+cc_binary(
     name = "spirv-cfg",
     srcs = [
         "tools/cfg/bin_to_dot.cpp",
@@ -465,6 +501,13 @@
     ["test/link/*.cpp"],
 )]
 
+[lint_test(
+    name = f[10:-4],  # strip test/lint/, .cpp
+    srcs = [f],
+) for f in glob(
+    ["test/lint/*.cpp"],
+)]
+
 [opt_test(
     name = f[9:-4],  # strip test/opt/, .cpp
     srcs = [f],
diff --git a/BUILD.gn b/BUILD.gn
index 845eb29..309d513 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -16,9 +16,11 @@
 import("//build_overrides/spirv_tools.gni")
 if (build_with_chromium) {
   import("//testing/test.gni")
+  import("//third_party/protobuf/proto_library.gni")
 }
 
 spirv_headers = spirv_tools_spirv_headers_dir
+spirv_is_winuwp = is_win && target_os == "winuwp"
 
 template("spvtools_core_tables") {
   assert(defined(invoker.version), "Need version in $target_name generation.")
@@ -32,13 +34,14 @@
         "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json"
     core_insts_file = "${target_gen_dir}/core.insts-$version.inc"
     operand_kinds_file = "${target_gen_dir}/operand.kinds-$version.inc"
-    debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
+    debuginfo_insts_file =
+        "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
     cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json"
 
     sources = [
+      cldebuginfo100_insts_file,
       core_json_file,
       debuginfo_insts_file,
-      cldebuginfo100_insts_file,
     ]
     outputs = [
       core_insts_file,
@@ -69,7 +72,8 @@
 
     core_json_file =
         "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json"
-    debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
+    debuginfo_insts_file =
+        "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
     cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json"
 
     extension_enum_file = "${target_gen_dir}/extension_enum.inc"
@@ -110,7 +114,8 @@
     core_json_file =
         "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json"
     glsl_json_file = "${spirv_headers}/include/spirv/${version}/extinst.glsl.std.450.grammar.json"
-    debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
+    debuginfo_insts_file =
+        "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
     cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json"
 
     glsl_insts_file = "${target_gen_dir}/glsl.std.450.insts.inc"
@@ -133,9 +138,7 @@
       debuginfo_insts_file,
       cldebuginfo100_insts_file,
     ]
-    outputs = [
-      glsl_insts_file,
-    ]
+    outputs = [ glsl_insts_file ]
   }
 }
 
@@ -150,7 +153,8 @@
     core_json_file =
         "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json"
     opencl_json_file = "${spirv_headers}/include/spirv/${version}/extinst.opencl.std.100.grammar.json"
-    debuginfo_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
+    debuginfo_insts_file =
+        "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
     cldebuginfo100_insts_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json"
 
     opencl_insts_file = "${target_gen_dir}/opencl.std.insts.inc"
@@ -173,9 +177,7 @@
       debuginfo_insts_file,
       cldebuginfo100_insts_file,
     ]
-    outputs = [
-      opencl_insts_file,
-    ]
+    outputs = [ opencl_insts_file ]
   }
 }
 
@@ -194,12 +196,8 @@
       "--extinst-output-path",
       rebase_path(extinst_output_path, root_build_dir),
     ]
-    inputs = [
-      invoker.grammar_file,
-    ]
-    outputs = [
-      "${extinst_output_path}",
-    ]
+    inputs = [ invoker.grammar_file ]
+    outputs = [ "${extinst_output_path}" ]
   }
 }
 
@@ -210,7 +208,8 @@
     script = "utils/generate_grammar_tables.py"
 
     name = invoker.name
-    extinst_vendor_grammar = "${spirv_headers}/include/spirv/unified1/extinst.${name}.grammar.json"
+    extinst_vendor_grammar =
+        "${spirv_headers}/include/spirv/unified1/extinst.${name}.grammar.json"
     extinst_file = "${target_gen_dir}/${name}.insts.inc"
 
     args = [
@@ -219,14 +218,10 @@
       "--vendor-insts-output",
       rebase_path(extinst_file, root_build_dir),
       "--vendor-operand-kind-prefix",
-      invoker.operand_kind_prefix
+      invoker.operand_kind_prefix,
     ]
-    inputs = [
-      extinst_vendor_grammar,
-    ]
-    outputs = [
-      extinst_file,
-    ]
+    inputs = [ extinst_vendor_grammar ]
+    outputs = [ extinst_file ]
   }
 }
 
@@ -237,12 +232,8 @@
   xml_file = "${spirv_headers}/include/spirv/spir-v.xml"
   inc_file = "${target_gen_dir}/generators.inc"
 
-  sources = [
-    xml_file,
-  ]
-  outputs = [
-    inc_file,
-  ]
+  sources = [ xml_file ]
+  outputs = [ inc_file ]
   args = [
     "--xml",
     rebase_path(xml_file, root_build_dir),
@@ -257,9 +248,7 @@
   src_dir = "."
   inc_file = "${target_gen_dir}/build-version.inc"
 
-  outputs = [
-    inc_file,
-  ]
+  outputs = [ inc_file ]
   args = [
     rebase_path(src_dir, root_build_dir),
     rebase_path(inc_file, root_build_dir),
@@ -280,21 +269,51 @@
 }
 spvtools_language_header("debuginfo") {
   name = "DebugInfo"
-  grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
+  grammar_file =
+      "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
 }
 spvtools_language_header("cldebuginfo100") {
   name = "OpenCLDebugInfo100"
   grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json"
 }
+spvtools_language_header("vkdebuginfo100") {
+  name = "NonSemanticShaderDebugInfo100"
+  grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json"
+}
 
 spvtools_vendor_tables = [
-  ["spv-amd-shader-explicit-vertex-parameter", "...nil..."],
-  ["spv-amd-shader-trinary-minmax", "...nil..."],
-  ["spv-amd-gcn-shader", "...nil..."],
-  ["spv-amd-shader-ballot", "...nil..."],
-  ["debuginfo", "...nil..."],
-  ["opencl.debuginfo.100", "CLDEBUG100_"],
-  ["nonsemantic.clspvreflection", "...nil..."],
+  [
+    "spv-amd-shader-explicit-vertex-parameter",
+    "...nil...",
+  ],
+  [
+    "spv-amd-shader-trinary-minmax",
+    "...nil...",
+  ],
+  [
+    "spv-amd-gcn-shader",
+    "...nil...",
+  ],
+  [
+    "spv-amd-shader-ballot",
+    "...nil...",
+  ],
+  [
+    "debuginfo",
+    "...nil...",
+  ],
+  [
+    "opencl.debuginfo.100",
+    "CLDEBUG100_",
+  ],
+  [
+    "nonsemantic.clspvreflection",
+    "...nil...",
+  ],
+  [
+    "nonsemantic.shader.debuginfo.100",
+    "SHDEBUG100_",
+  ],
 ]
 
 foreach(table_def, spvtools_vendor_tables) {
@@ -308,20 +327,32 @@
   include_dirs = [ "include" ]
 }
 
+config("spvtools_include_gen_dirs") {
+  include_dirs = [ "$target_gen_dir" ]
+}
+
 config("spvtools_internal_config") {
   include_dirs = [
     ".",
-    "$target_gen_dir",
     "${spirv_headers}/include",
   ]
 
-  configs = [ ":spvtools_public_config" ]
+  configs = [
+    ":spvtools_public_config",
+    ":spvtools_include_gen_dirs",
+  ]
 
+  cflags = []
   if (is_clang) {
-    cflags = [
+    cflags += [
       "-Wno-implicit-fallthrough",
       "-Wno-newline-eof",
+      "-Wno-unreachable-code-break",
+      "-Wno-unreachable-code-return",
     ]
+  } else if (!is_win) {
+    # Work around a false-positive on a Skia GCC 10 builder.
+    cflags += [ "-Wno-format-truncation" ]
   }
 }
 
@@ -337,13 +368,22 @@
   public_configs = [ ":spvtools_public_config" ]
 }
 
+group("spvtools_language_headers") {
+  public_deps = [
+    ":spvtools_language_header_cldebuginfo100",
+    ":spvtools_language_header_debuginfo",
+    ":spvtools_language_header_vkdebuginfo100",
+  ]
+}
+
 static_library("spvtools") {
   deps = [
     ":spvtools_core_tables_unified1",
     ":spvtools_generators_inc",
     ":spvtools_glsl_tables_glsl1-0",
-    ":spvtools_language_header_debuginfo",
     ":spvtools_language_header_cldebuginfo100",
+    ":spvtools_language_header_debuginfo",
+    ":spvtools_language_header_vkdebuginfo100",
     ":spvtools_opencl_tables_opencl1-0",
   ]
   foreach(table_def, spvtools_vendor_tables) {
@@ -357,6 +397,7 @@
     "source/binary.cpp",
     "source/binary.h",
     "source/cfa.h",
+    "source/common_debug_info.h",
     "source/diagnostic.cpp",
     "source/diagnostic.h",
     "source/disassemble.cpp",
@@ -388,8 +429,12 @@
     "source/spirv_definition.h",
     "source/spirv_endian.cpp",
     "source/spirv_endian.h",
+    "source/spirv_fuzzer_options.cpp",
+    "source/spirv_fuzzer_options.h",
     "source/spirv_optimizer_options.cpp",
     "source/spirv_optimizer_options.h",
+    "source/spirv_reducer_options.cpp",
+    "source/spirv_reducer_options.h",
     "source/spirv_target_env.cpp",
     "source/spirv_target_env.h",
     "source/spirv_validator_options.cpp",
@@ -485,10 +530,9 @@
     ":spvtools",
     ":spvtools_language_header_cldebuginfo100",
     ":spvtools_language_header_debuginfo",
+    ":spvtools_language_header_vkdebuginfo100",
   ]
-  public_deps = [
-    ":spvtools_headers",
-  ]
+  public_deps = [ ":spvtools_headers" ]
 
   if (build_with_chromium) {
     configs -= [ "//build/config/compiler:chromium_code" ]
@@ -529,24 +573,32 @@
     "source/opt/const_folding_rules.h",
     "source/opt/constants.cpp",
     "source/opt/constants.h",
+    "source/opt/control_dependence.cpp",
+    "source/opt/control_dependence.h",
     "source/opt/convert_to_half_pass.cpp",
     "source/opt/convert_to_half_pass.h",
+    "source/opt/convert_to_sampled_image_pass.cpp",
+    "source/opt/convert_to_sampled_image_pass.h",
     "source/opt/copy_prop_arrays.cpp",
     "source/opt/copy_prop_arrays.h",
+    "source/opt/dataflow.cpp",
+    "source/opt/dataflow.h",
     "source/opt/dead_branch_elim_pass.cpp",
     "source/opt/dead_branch_elim_pass.h",
     "source/opt/dead_insert_elim_pass.cpp",
     "source/opt/dead_insert_elim_pass.h",
     "source/opt/dead_variable_elimination.cpp",
     "source/opt/dead_variable_elimination.h",
-    "source/opt/decoration_manager.cpp",
-    "source/opt/decoration_manager.h",
     "source/opt/debug_info_manager.cpp",
     "source/opt/debug_info_manager.h",
+    "source/opt/decoration_manager.cpp",
+    "source/opt/decoration_manager.h",
     "source/opt/def_use_manager.cpp",
     "source/opt/def_use_manager.h",
     "source/opt/desc_sroa.cpp",
     "source/opt/desc_sroa.h",
+    "source/opt/desc_sroa_util.cpp",
+    "source/opt/desc_sroa_util.h",
     "source/opt/dominator_analysis.cpp",
     "source/opt/dominator_analysis.h",
     "source/opt/dominator_tree.cpp",
@@ -598,6 +650,8 @@
     "source/opt/instruction_list.h",
     "source/opt/instrument_pass.cpp",
     "source/opt/instrument_pass.h",
+    "source/opt/interp_fixup_pass.cpp",
+    "source/opt/interp_fixup_pass.h",
     "source/opt/ir_builder.h",
     "source/opt/ir_context.cpp",
     "source/opt/ir_context.h",
@@ -662,6 +716,10 @@
     "source/opt/relax_float_ops_pass.h",
     "source/opt/remove_duplicates_pass.cpp",
     "source/opt/remove_duplicates_pass.h",
+    "source/opt/remove_unused_interface_variables_pass.cpp",
+    "source/opt/remove_unused_interface_variables_pass.h",
+    "source/opt/replace_desc_array_access_using_var_index.cpp",
+    "source/opt/replace_desc_array_access_using_var_index.h",
     "source/opt/replace_invalid_opc.cpp",
     "source/opt/replace_invalid_opc.h",
     "source/opt/scalar_analysis.cpp",
@@ -705,12 +763,13 @@
 
   deps = [
     ":spvtools",
-    ":spvtools_language_header_cldebuginfo100",
     ":spvtools_language_header_debuginfo",
     ":spvtools_vendor_tables_spv-amd-shader-ballot",
   ]
   public_deps = [
     ":spvtools_headers",
+    ":spvtools_language_header_cldebuginfo100",
+    ":spvtools_language_header_vkdebuginfo100",
   ]
 
   if (build_with_chromium) {
@@ -721,17 +780,13 @@
 }
 
 static_library("spvtools_link") {
-  sources = [
-    "source/link/linker.cpp",
-  ]
+  sources = [ "source/link/linker.cpp" ]
   deps = [
     ":spvtools",
     ":spvtools_opt",
     ":spvtools_val",
   ]
-  public_deps = [
-    ":spvtools_headers",
-  ]
+  public_deps = [ ":spvtools_headers" ]
   if (build_with_chromium) {
     configs -= [ "//build/config/compiler:chromium_code" ]
     configs += [ "//build/config/compiler:no_chromium_code" ]
@@ -793,20 +848,20 @@
     "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_construct_to_block_reduction_opportunity.cpp",
+    "source/reduce/structured_construct_to_block_reduction_opportunity.h",
+    "source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp",
+    "source/reduce/structured_construct_to_block_reduction_opportunity_finder.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_opt",
   ]
-  public_deps = [
-    ":spvtools_headers",
-  ]
+  public_deps = [ ":spvtools_headers" ]
   if (build_with_chromium) {
     configs -= [ "//build/config/compiler:chromium_code" ]
     configs += [ "//build/config/compiler:no_chromium_code" ]
@@ -814,6 +869,423 @@
   configs += [ ":spvtools_internal_config" ]
 }
 
+if (build_with_chromium) {
+  # The spirv-fuzz library is only built when in a Chromium checkout
+  # due to its dependency on protobuf.
+
+  proto_library("spvtools_fuzz_proto") {
+    sources = [ "source/fuzz/protobufs/spvtoolsfuzz.proto" ]
+    generate_python = false
+    use_protobuf_full = true
+  }
+
+  static_library("spvtools_fuzz") {
+    sources = [
+      "source/fuzz/added_function_reducer.cpp",
+      "source/fuzz/added_function_reducer.h",
+      "source/fuzz/available_instructions.cpp",
+      "source/fuzz/available_instructions.h",
+      "source/fuzz/call_graph.cpp",
+      "source/fuzz/call_graph.h",
+      "source/fuzz/comparator_deep_blocks_first.h",
+      "source/fuzz/counter_overflow_id_source.cpp",
+      "source/fuzz/counter_overflow_id_source.h",
+      "source/fuzz/data_descriptor.cpp",
+      "source/fuzz/data_descriptor.h",
+      "source/fuzz/equivalence_relation.h",
+      "source/fuzz/fact_manager/constant_uniform_facts.cpp",
+      "source/fuzz/fact_manager/constant_uniform_facts.h",
+      "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp",
+      "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h",
+      "source/fuzz/fact_manager/dead_block_facts.cpp",
+      "source/fuzz/fact_manager/dead_block_facts.h",
+      "source/fuzz/fact_manager/fact_manager.cpp",
+      "source/fuzz/fact_manager/fact_manager.h",
+      "source/fuzz/fact_manager/irrelevant_value_facts.cpp",
+      "source/fuzz/fact_manager/irrelevant_value_facts.h",
+      "source/fuzz/fact_manager/livesafe_function_facts.cpp",
+      "source/fuzz/fact_manager/livesafe_function_facts.h",
+      "source/fuzz/force_render_red.cpp",
+      "source/fuzz/force_render_red.h",
+      "source/fuzz/fuzzer.cpp",
+      "source/fuzz/fuzzer.h",
+      "source/fuzz/fuzzer_context.cpp",
+      "source/fuzz/fuzzer_context.h",
+      "source/fuzz/fuzzer_pass.cpp",
+      "source/fuzz/fuzzer_pass.h",
+      "source/fuzz/fuzzer_pass_add_access_chains.cpp",
+      "source/fuzz/fuzzer_pass_add_access_chains.h",
+      "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp",
+      "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h",
+      "source/fuzz/fuzzer_pass_add_composite_extract.cpp",
+      "source/fuzz/fuzzer_pass_add_composite_extract.h",
+      "source/fuzz/fuzzer_pass_add_composite_inserts.cpp",
+      "source/fuzz/fuzzer_pass_add_composite_inserts.h",
+      "source/fuzz/fuzzer_pass_add_composite_types.cpp",
+      "source/fuzz/fuzzer_pass_add_composite_types.h",
+      "source/fuzz/fuzzer_pass_add_copy_memory.cpp",
+      "source/fuzz/fuzzer_pass_add_copy_memory.h",
+      "source/fuzz/fuzzer_pass_add_dead_blocks.cpp",
+      "source/fuzz/fuzzer_pass_add_dead_blocks.h",
+      "source/fuzz/fuzzer_pass_add_dead_breaks.cpp",
+      "source/fuzz/fuzzer_pass_add_dead_breaks.h",
+      "source/fuzz/fuzzer_pass_add_dead_continues.cpp",
+      "source/fuzz/fuzzer_pass_add_dead_continues.h",
+      "source/fuzz/fuzzer_pass_add_equation_instructions.cpp",
+      "source/fuzz/fuzzer_pass_add_equation_instructions.h",
+      "source/fuzz/fuzzer_pass_add_function_calls.cpp",
+      "source/fuzz/fuzzer_pass_add_function_calls.h",
+      "source/fuzz/fuzzer_pass_add_global_variables.cpp",
+      "source/fuzz/fuzzer_pass_add_global_variables.h",
+      "source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp",
+      "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h",
+      "source/fuzz/fuzzer_pass_add_loads.cpp",
+      "source/fuzz/fuzzer_pass_add_loads.h",
+      "source/fuzz/fuzzer_pass_add_local_variables.cpp",
+      "source/fuzz/fuzzer_pass_add_local_variables.h",
+      "source/fuzz/fuzzer_pass_add_loop_preheaders.cpp",
+      "source/fuzz/fuzzer_pass_add_loop_preheaders.h",
+      "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp",
+      "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h",
+      "source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp",
+      "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h",
+      "source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp",
+      "source/fuzz/fuzzer_pass_add_opphi_synonyms.h",
+      "source/fuzz/fuzzer_pass_add_parameters.cpp",
+      "source/fuzz/fuzzer_pass_add_parameters.h",
+      "source/fuzz/fuzzer_pass_add_relaxed_decorations.cpp",
+      "source/fuzz/fuzzer_pass_add_relaxed_decorations.h",
+      "source/fuzz/fuzzer_pass_add_stores.cpp",
+      "source/fuzz/fuzzer_pass_add_stores.h",
+      "source/fuzz/fuzzer_pass_add_synonyms.cpp",
+      "source/fuzz/fuzzer_pass_add_synonyms.h",
+      "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.cpp",
+      "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h",
+      "source/fuzz/fuzzer_pass_adjust_branch_weights.cpp",
+      "source/fuzz/fuzzer_pass_adjust_branch_weights.h",
+      "source/fuzz/fuzzer_pass_adjust_function_controls.cpp",
+      "source/fuzz/fuzzer_pass_adjust_function_controls.h",
+      "source/fuzz/fuzzer_pass_adjust_loop_controls.cpp",
+      "source/fuzz/fuzzer_pass_adjust_loop_controls.h",
+      "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp",
+      "source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h",
+      "source/fuzz/fuzzer_pass_adjust_selection_controls.cpp",
+      "source/fuzz/fuzzer_pass_adjust_selection_controls.h",
+      "source/fuzz/fuzzer_pass_apply_id_synonyms.cpp",
+      "source/fuzz/fuzzer_pass_apply_id_synonyms.h",
+      "source/fuzz/fuzzer_pass_construct_composites.cpp",
+      "source/fuzz/fuzzer_pass_construct_composites.h",
+      "source/fuzz/fuzzer_pass_copy_objects.cpp",
+      "source/fuzz/fuzzer_pass_copy_objects.h",
+      "source/fuzz/fuzzer_pass_donate_modules.cpp",
+      "source/fuzz/fuzzer_pass_donate_modules.h",
+      "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp",
+      "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h",
+      "source/fuzz/fuzzer_pass_expand_vector_reductions.cpp",
+      "source/fuzz/fuzzer_pass_expand_vector_reductions.h",
+      "source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp",
+      "source/fuzz/fuzzer_pass_flatten_conditional_branches.h",
+      "source/fuzz/fuzzer_pass_inline_functions.cpp",
+      "source/fuzz/fuzzer_pass_inline_functions.h",
+      "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp",
+      "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h",
+      "source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp",
+      "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h",
+      "source/fuzz/fuzzer_pass_invert_comparison_operators.cpp",
+      "source/fuzz/fuzzer_pass_invert_comparison_operators.h",
+      "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp",
+      "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h",
+      "source/fuzz/fuzzer_pass_merge_blocks.cpp",
+      "source/fuzz/fuzzer_pass_merge_blocks.h",
+      "source/fuzz/fuzzer_pass_merge_function_returns.cpp",
+      "source/fuzz/fuzzer_pass_merge_function_returns.h",
+      "source/fuzz/fuzzer_pass_mutate_pointers.cpp",
+      "source/fuzz/fuzzer_pass_mutate_pointers.h",
+      "source/fuzz/fuzzer_pass_obfuscate_constants.cpp",
+      "source/fuzz/fuzzer_pass_obfuscate_constants.h",
+      "source/fuzz/fuzzer_pass_outline_functions.cpp",
+      "source/fuzz/fuzzer_pass_outline_functions.h",
+      "source/fuzz/fuzzer_pass_permute_blocks.cpp",
+      "source/fuzz/fuzzer_pass_permute_blocks.h",
+      "source/fuzz/fuzzer_pass_permute_function_parameters.cpp",
+      "source/fuzz/fuzzer_pass_permute_function_parameters.h",
+      "source/fuzz/fuzzer_pass_permute_function_variables.cpp",
+      "source/fuzz/fuzzer_pass_permute_function_variables.h",
+      "source/fuzz/fuzzer_pass_permute_instructions.cpp",
+      "source/fuzz/fuzzer_pass_permute_instructions.h",
+      "source/fuzz/fuzzer_pass_permute_phi_operands.cpp",
+      "source/fuzz/fuzzer_pass_permute_phi_operands.h",
+      "source/fuzz/fuzzer_pass_propagate_instructions_down.cpp",
+      "source/fuzz/fuzzer_pass_propagate_instructions_down.h",
+      "source/fuzz/fuzzer_pass_propagate_instructions_up.cpp",
+      "source/fuzz/fuzzer_pass_propagate_instructions_up.h",
+      "source/fuzz/fuzzer_pass_push_ids_through_variables.cpp",
+      "source/fuzz/fuzzer_pass_push_ids_through_variables.h",
+      "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp",
+      "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h",
+      "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp",
+      "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h",
+      "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp",
+      "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h",
+      "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp",
+      "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h",
+      "source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp",
+      "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h",
+      "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp",
+      "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h",
+      "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp",
+      "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h",
+      "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp",
+      "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h",
+      "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp",
+      "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h",
+      "source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp",
+      "source/fuzz/fuzzer_pass_replace_parameter_with_global.h",
+      "source/fuzz/fuzzer_pass_replace_params_with_struct.cpp",
+      "source/fuzz/fuzzer_pass_replace_params_with_struct.h",
+      "source/fuzz/fuzzer_pass_split_blocks.cpp",
+      "source/fuzz/fuzzer_pass_split_blocks.h",
+      "source/fuzz/fuzzer_pass_swap_commutable_operands.cpp",
+      "source/fuzz/fuzzer_pass_swap_commutable_operands.h",
+      "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp",
+      "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h",
+      "source/fuzz/fuzzer_pass_swap_functions.cpp",
+      "source/fuzz/fuzzer_pass_swap_functions.h",
+      "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp",
+      "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h",
+      "source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp",
+      "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h",
+      "source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp",
+      "source/fuzz/fuzzer_pass_wrap_vector_synonym.h",
+      "source/fuzz/fuzzer_util.cpp",
+      "source/fuzz/fuzzer_util.h",
+      "source/fuzz/id_use_descriptor.cpp",
+      "source/fuzz/id_use_descriptor.h",
+      "source/fuzz/instruction_descriptor.cpp",
+      "source/fuzz/instruction_descriptor.h",
+      "source/fuzz/instruction_message.cpp",
+      "source/fuzz/instruction_message.h",
+      "source/fuzz/overflow_id_source.cpp",
+      "source/fuzz/overflow_id_source.h",
+      "source/fuzz/pass_management/repeated_pass_instances.h",
+      "source/fuzz/pass_management/repeated_pass_manager.cpp",
+      "source/fuzz/pass_management/repeated_pass_manager.h",
+      "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp",
+      "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h",
+      "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp",
+      "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h",
+      "source/fuzz/pass_management/repeated_pass_manager_simple.cpp",
+      "source/fuzz/pass_management/repeated_pass_manager_simple.h",
+      "source/fuzz/pass_management/repeated_pass_recommender.cpp",
+      "source/fuzz/pass_management/repeated_pass_recommender.h",
+      "source/fuzz/pass_management/repeated_pass_recommender_standard.cpp",
+      "source/fuzz/pass_management/repeated_pass_recommender_standard.h",
+      "source/fuzz/protobufs/spirvfuzz_protobufs.h",
+      "source/fuzz/pseudo_random_generator.cpp",
+      "source/fuzz/pseudo_random_generator.h",
+      "source/fuzz/random_generator.cpp",
+      "source/fuzz/random_generator.h",
+      "source/fuzz/replayer.cpp",
+      "source/fuzz/replayer.h",
+      "source/fuzz/shrinker.cpp",
+      "source/fuzz/shrinker.h",
+      "source/fuzz/transformation.cpp",
+      "source/fuzz/transformation.h",
+      "source/fuzz/transformation_access_chain.cpp",
+      "source/fuzz/transformation_access_chain.h",
+      "source/fuzz/transformation_add_bit_instruction_synonym.cpp",
+      "source/fuzz/transformation_add_bit_instruction_synonym.h",
+      "source/fuzz/transformation_add_constant_boolean.cpp",
+      "source/fuzz/transformation_add_constant_boolean.h",
+      "source/fuzz/transformation_add_constant_composite.cpp",
+      "source/fuzz/transformation_add_constant_composite.h",
+      "source/fuzz/transformation_add_constant_null.cpp",
+      "source/fuzz/transformation_add_constant_null.h",
+      "source/fuzz/transformation_add_constant_scalar.cpp",
+      "source/fuzz/transformation_add_constant_scalar.h",
+      "source/fuzz/transformation_add_copy_memory.cpp",
+      "source/fuzz/transformation_add_copy_memory.h",
+      "source/fuzz/transformation_add_dead_block.cpp",
+      "source/fuzz/transformation_add_dead_block.h",
+      "source/fuzz/transformation_add_dead_break.cpp",
+      "source/fuzz/transformation_add_dead_break.h",
+      "source/fuzz/transformation_add_dead_continue.cpp",
+      "source/fuzz/transformation_add_dead_continue.h",
+      "source/fuzz/transformation_add_early_terminator_wrapper.cpp",
+      "source/fuzz/transformation_add_early_terminator_wrapper.h",
+      "source/fuzz/transformation_add_function.cpp",
+      "source/fuzz/transformation_add_function.h",
+      "source/fuzz/transformation_add_global_undef.cpp",
+      "source/fuzz/transformation_add_global_undef.h",
+      "source/fuzz/transformation_add_global_variable.cpp",
+      "source/fuzz/transformation_add_global_variable.h",
+      "source/fuzz/transformation_add_image_sample_unused_components.cpp",
+      "source/fuzz/transformation_add_image_sample_unused_components.h",
+      "source/fuzz/transformation_add_local_variable.cpp",
+      "source/fuzz/transformation_add_local_variable.h",
+      "source/fuzz/transformation_add_loop_preheader.cpp",
+      "source/fuzz/transformation_add_loop_preheader.h",
+      "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp",
+      "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h",
+      "source/fuzz/transformation_add_no_contraction_decoration.cpp",
+      "source/fuzz/transformation_add_no_contraction_decoration.h",
+      "source/fuzz/transformation_add_opphi_synonym.cpp",
+      "source/fuzz/transformation_add_opphi_synonym.h",
+      "source/fuzz/transformation_add_parameter.cpp",
+      "source/fuzz/transformation_add_parameter.h",
+      "source/fuzz/transformation_add_relaxed_decoration.cpp",
+      "source/fuzz/transformation_add_relaxed_decoration.h",
+      "source/fuzz/transformation_add_spec_constant_op.cpp",
+      "source/fuzz/transformation_add_spec_constant_op.h",
+      "source/fuzz/transformation_add_synonym.cpp",
+      "source/fuzz/transformation_add_synonym.h",
+      "source/fuzz/transformation_add_type_array.cpp",
+      "source/fuzz/transformation_add_type_array.h",
+      "source/fuzz/transformation_add_type_boolean.cpp",
+      "source/fuzz/transformation_add_type_boolean.h",
+      "source/fuzz/transformation_add_type_float.cpp",
+      "source/fuzz/transformation_add_type_float.h",
+      "source/fuzz/transformation_add_type_function.cpp",
+      "source/fuzz/transformation_add_type_function.h",
+      "source/fuzz/transformation_add_type_int.cpp",
+      "source/fuzz/transformation_add_type_int.h",
+      "source/fuzz/transformation_add_type_matrix.cpp",
+      "source/fuzz/transformation_add_type_matrix.h",
+      "source/fuzz/transformation_add_type_pointer.cpp",
+      "source/fuzz/transformation_add_type_pointer.h",
+      "source/fuzz/transformation_add_type_struct.cpp",
+      "source/fuzz/transformation_add_type_struct.h",
+      "source/fuzz/transformation_add_type_vector.cpp",
+      "source/fuzz/transformation_add_type_vector.h",
+      "source/fuzz/transformation_adjust_branch_weights.cpp",
+      "source/fuzz/transformation_adjust_branch_weights.h",
+      "source/fuzz/transformation_composite_construct.cpp",
+      "source/fuzz/transformation_composite_construct.h",
+      "source/fuzz/transformation_composite_extract.cpp",
+      "source/fuzz/transformation_composite_extract.h",
+      "source/fuzz/transformation_composite_insert.cpp",
+      "source/fuzz/transformation_composite_insert.h",
+      "source/fuzz/transformation_compute_data_synonym_fact_closure.cpp",
+      "source/fuzz/transformation_compute_data_synonym_fact_closure.h",
+      "source/fuzz/transformation_context.cpp",
+      "source/fuzz/transformation_context.h",
+      "source/fuzz/transformation_duplicate_region_with_selection.cpp",
+      "source/fuzz/transformation_duplicate_region_with_selection.h",
+      "source/fuzz/transformation_equation_instruction.cpp",
+      "source/fuzz/transformation_equation_instruction.h",
+      "source/fuzz/transformation_expand_vector_reduction.cpp",
+      "source/fuzz/transformation_expand_vector_reduction.h",
+      "source/fuzz/transformation_flatten_conditional_branch.cpp",
+      "source/fuzz/transformation_flatten_conditional_branch.h",
+      "source/fuzz/transformation_function_call.cpp",
+      "source/fuzz/transformation_function_call.h",
+      "source/fuzz/transformation_inline_function.cpp",
+      "source/fuzz/transformation_inline_function.h",
+      "source/fuzz/transformation_invert_comparison_operator.cpp",
+      "source/fuzz/transformation_invert_comparison_operator.h",
+      "source/fuzz/transformation_load.cpp",
+      "source/fuzz/transformation_load.h",
+      "source/fuzz/transformation_make_vector_operation_dynamic.cpp",
+      "source/fuzz/transformation_make_vector_operation_dynamic.h",
+      "source/fuzz/transformation_merge_blocks.cpp",
+      "source/fuzz/transformation_merge_blocks.h",
+      "source/fuzz/transformation_merge_function_returns.cpp",
+      "source/fuzz/transformation_merge_function_returns.h",
+      "source/fuzz/transformation_move_block_down.cpp",
+      "source/fuzz/transformation_move_block_down.h",
+      "source/fuzz/transformation_move_instruction_down.cpp",
+      "source/fuzz/transformation_move_instruction_down.h",
+      "source/fuzz/transformation_mutate_pointer.cpp",
+      "source/fuzz/transformation_mutate_pointer.h",
+      "source/fuzz/transformation_outline_function.cpp",
+      "source/fuzz/transformation_outline_function.h",
+      "source/fuzz/transformation_permute_function_parameters.cpp",
+      "source/fuzz/transformation_permute_function_parameters.h",
+      "source/fuzz/transformation_permute_phi_operands.cpp",
+      "source/fuzz/transformation_permute_phi_operands.h",
+      "source/fuzz/transformation_propagate_instruction_down.cpp",
+      "source/fuzz/transformation_propagate_instruction_down.h",
+      "source/fuzz/transformation_propagate_instruction_up.cpp",
+      "source/fuzz/transformation_propagate_instruction_up.h",
+      "source/fuzz/transformation_push_id_through_variable.cpp",
+      "source/fuzz/transformation_push_id_through_variable.h",
+      "source/fuzz/transformation_record_synonymous_constants.cpp",
+      "source/fuzz/transformation_record_synonymous_constants.h",
+      "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp",
+      "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h",
+      "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp",
+      "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h",
+      "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.cpp",
+      "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h",
+      "source/fuzz/transformation_replace_constant_with_uniform.cpp",
+      "source/fuzz/transformation_replace_constant_with_uniform.h",
+      "source/fuzz/transformation_replace_copy_memory_with_load_store.cpp",
+      "source/fuzz/transformation_replace_copy_memory_with_load_store.h",
+      "source/fuzz/transformation_replace_copy_object_with_store_load.cpp",
+      "source/fuzz/transformation_replace_copy_object_with_store_load.h",
+      "source/fuzz/transformation_replace_id_with_synonym.cpp",
+      "source/fuzz/transformation_replace_id_with_synonym.h",
+      "source/fuzz/transformation_replace_irrelevant_id.cpp",
+      "source/fuzz/transformation_replace_irrelevant_id.h",
+      "source/fuzz/transformation_replace_linear_algebra_instruction.cpp",
+      "source/fuzz/transformation_replace_linear_algebra_instruction.h",
+      "source/fuzz/transformation_replace_load_store_with_copy_memory.cpp",
+      "source/fuzz/transformation_replace_load_store_with_copy_memory.h",
+      "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp",
+      "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h",
+      "source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp",
+      "source/fuzz/transformation_replace_opselect_with_conditional_branch.h",
+      "source/fuzz/transformation_replace_parameter_with_global.cpp",
+      "source/fuzz/transformation_replace_parameter_with_global.h",
+      "source/fuzz/transformation_replace_params_with_struct.cpp",
+      "source/fuzz/transformation_replace_params_with_struct.h",
+      "source/fuzz/transformation_set_function_control.cpp",
+      "source/fuzz/transformation_set_function_control.h",
+      "source/fuzz/transformation_set_loop_control.cpp",
+      "source/fuzz/transformation_set_loop_control.h",
+      "source/fuzz/transformation_set_memory_operands_mask.cpp",
+      "source/fuzz/transformation_set_memory_operands_mask.h",
+      "source/fuzz/transformation_set_selection_control.cpp",
+      "source/fuzz/transformation_set_selection_control.h",
+      "source/fuzz/transformation_split_block.cpp",
+      "source/fuzz/transformation_split_block.h",
+      "source/fuzz/transformation_store.cpp",
+      "source/fuzz/transformation_store.h",
+      "source/fuzz/transformation_swap_commutable_operands.cpp",
+      "source/fuzz/transformation_swap_commutable_operands.h",
+      "source/fuzz/transformation_swap_conditional_branch_operands.cpp",
+      "source/fuzz/transformation_swap_conditional_branch_operands.h",
+      "source/fuzz/transformation_swap_function_variables.cpp",
+      "source/fuzz/transformation_swap_function_variables.h",
+      "source/fuzz/transformation_swap_two_functions.cpp",
+      "source/fuzz/transformation_swap_two_functions.h",
+      "source/fuzz/transformation_toggle_access_chain_instruction.cpp",
+      "source/fuzz/transformation_toggle_access_chain_instruction.h",
+      "source/fuzz/transformation_vector_shuffle.cpp",
+      "source/fuzz/transformation_vector_shuffle.h",
+      "source/fuzz/transformation_wrap_early_terminator_in_function.cpp",
+      "source/fuzz/transformation_wrap_early_terminator_in_function.h",
+      "source/fuzz/transformation_wrap_region_in_selection.cpp",
+      "source/fuzz/transformation_wrap_region_in_selection.h",
+      "source/fuzz/transformation_wrap_vector_synonym.cpp",
+      "source/fuzz/transformation_wrap_vector_synonym.h",
+      "source/fuzz/uniform_buffer_element_descriptor.cpp",
+      "source/fuzz/uniform_buffer_element_descriptor.h",
+    ]
+    deps = [
+      ":spvtools",
+      ":spvtools_fuzz_proto",
+      ":spvtools_opt",
+      ":spvtools_reduce",
+      "//third_party/protobuf:protobuf_full",
+    ]
+    public_deps = [ ":spvtools_headers" ]
+    configs -= [ "//build/config/compiler:chromium_code" ]
+    configs += [ "//build/config/compiler:no_chromium_code" ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+}
+
 group("SPIRV-Tools") {
   public_deps = [
     ":spvtools",
@@ -841,10 +1313,10 @@
       "test/comment_test.cpp",
       "test/enum_set_test.cpp",
       "test/enum_string_mapping_test.cpp",
+      "test/ext_inst.cldebug100_test.cpp",
       "test/ext_inst.debuginfo_test.cpp",
       "test/ext_inst.glsl_test.cpp",
       "test/ext_inst.opencl_test.cpp",
-      "test/ext_inst.cldebug100_test.cpp",
       "test/fix_word_test.cpp",
       "test/generator_magic_number_test.cpp",
       "test/hex_float_test.cpp",
@@ -891,8 +1363,9 @@
 
     deps = [
       ":spvtools",
-      ":spvtools_language_header_debuginfo",
       ":spvtools_language_header_cldebuginfo100",
+      ":spvtools_language_header_debuginfo",
+      ":spvtools_language_header_vkdebuginfo100",
       ":spvtools_val",
       "//testing/gmock",
       "//testing/gtest",
@@ -912,9 +1385,7 @@
 if (spirv_tools_standalone) {
   group("fuzzers") {
     testonly = true
-    deps = [
-      "test/fuzzers",
-    ]
+    deps = [ "test/fuzzers" ]
   }
 }
 
@@ -923,16 +1394,12 @@
     "tools/util/cli_consumer.cpp",
     "tools/util/cli_consumer.h",
   ]
-  deps = [
-    ":spvtools_headers",
-  ]
+  deps = [ ":spvtools_headers" ]
   configs += [ ":spvtools_internal_config" ]
 }
 
 source_set("spvtools_software_version") {
-  sources = [
-    "source/software_version.cpp",
-  ]
+  sources = [ "source/software_version.cpp" ]
   deps = [
     ":spvtools_build_version",
     ":spvtools_headers",
@@ -941,9 +1408,7 @@
 }
 
 executable("spirv-as") {
-  sources = [
-    "tools/as/as.cpp",
-  ]
+  sources = [ "tools/as/as.cpp" ]
   deps = [
     ":spvtools",
     ":spvtools_software_version",
@@ -952,9 +1417,7 @@
 }
 
 executable("spirv-dis") {
-  sources = [
-    "tools/dis/dis.cpp",
-  ]
+  sources = [ "tools/dis/dis.cpp" ]
   deps = [
     ":spvtools",
     ":spvtools_software_version",
@@ -963,9 +1426,7 @@
 }
 
 executable("spirv-val") {
-  sources = [
-    "tools/val/val.cpp",
-  ]
+  sources = [ "tools/val/val.cpp" ]
   deps = [
     ":spvtools",
     ":spvtools_software_version",
@@ -989,9 +1450,7 @@
 }
 
 executable("spirv-opt") {
-  sources = [
-    "tools/opt/opt.cpp",
-  ]
+  sources = [ "tools/opt/opt.cpp" ]
   deps = [
     ":spvtools",
     ":spvtools_opt",
@@ -1003,9 +1462,7 @@
 }
 
 executable("spirv-link") {
-  sources = [
-    "tools/link/linker.cpp",
-  ]
+  sources = [ "tools/link/linker.cpp" ]
   deps = [
     ":spvtools",
     ":spvtools_link",
@@ -1016,12 +1473,33 @@
   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",
+if (!is_ios && !spirv_is_winuwp && build_with_chromium) {
+  # iOS and UWP do not allow std::system calls which spirv-fuzz
+  # requires. Additionally, spirv-fuzz is only built when in a
+  # Chromium checkout due to its dependency on protobuf.
+
+  executable("spirv-fuzz") {
+    sources = [ "tools/fuzz/fuzz.cpp" ]
+    deps = [
+      ":spvtools",
+      ":spvtools_fuzz",
+      ":spvtools_opt",
+      ":spvtools_reduce",
+      ":spvtools_software_version",
+      ":spvtools_util_cli_consumer",
+      ":spvtools_val",
+      "//third_party/protobuf:protobuf_full",
     ]
+    configs += [ ":spvtools_internal_config" ]
+  }
+}
+
+if (!is_ios && !spirv_is_winuwp) {
+  # iOS and UWP do not allow std::system calls which spirv-reduce
+  # requires.
+
+  executable("spirv-reduce") {
+    sources = [ "tools/reduce/reduce.cpp" ]
     deps = [
       ":spvtools",
       ":spvtools_opt",
@@ -1043,7 +1521,10 @@
     ":spirv-opt",
     ":spirv-val",
   ]
-  if (!is_ios) {
+  if (!is_ios && !spirv_is_winuwp && build_with_chromium) {
+    deps += [ ":spirv-fuzz" ]
+  }
+  if (!is_ios && !spirv_is_winuwp) {
     deps += [ ":spirv-reduce" ]
   }
 }
diff --git a/CHANGES b/CHANGES
index d7a49f7..c905ad4 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,118 @@
 Revision history for SPIRV-Tools
 
-v2020.7-dev 2020-12-07
- - Start v2020.7-dev
+v2021.4-dev 2021-08-25
+  - Add a WebAssembly build
+
+v2021.3 2021-08-24
+ - General
+    - Initial support for SPV_KHR_integer_dot_product (#4327)
+    - Add non-semantic vulkan extended instruction set (#4362)
+    - Add common enum for debug info instructions from either opencl or vulkan (#4377)
+ - Validator
+    - Add validation for SPV_EXT_shader_atomic_float16_add (#4325)
+    - Disallow loading a runtime-sized array (#4473)
+    - spirv-val: Validate vulkan debug info similarly to opencl debug info (#4466)
+ - Optimizer
+    - spirv-opt: support SPV_EXT_shader_image_int64 (#4379)
+    - spirv-opt: Add dataflow analysis framework (#4402)
+    - Add control dependence analysis to opt (#4380)
+    - Add spirv-opt convert-to-sampled-image pass (#4340)
+    - spirv-opt: Add handling of vulkan debug info to DebugInfoManager (#4423)
+ - Fuzz
+    - spirv-fuzz: support AtomicLoad (#4330)
+    - spirv-fuzz: Support AtomicStore (#4440)
+    - spirv-fuzz: TransformationWrapVectorSynonym that rewrites scalar operations using vectors (#4376)
+    - spirv-fuzz: Add minimal SPIR-V example to test shaders (#4415)
+    - spirv-fuzz: support building using gn (#4365)
+ - Linter
+    - Add new target for spirv-lint (#4446)
+    - spirv-lint: add basic CLI argument handling (#4478)
+    - Add divergence analysis to linter (#4465)
+
+v2021.2 2021-06-18
+ - General
+    - Support SPV_KHR_subgroup_uniform_control_flow (#4318)
+    - Support Intel extensions for fixed point and hls-float (#4321)
+    - Fix crash when optimizing shaders with DebugPrintf (#4280)
+
+ - Validator
+    - Support Vulkan Storage Class for Execution Model (#4212)
+
+ - Optimizer
+    - Handle SPV_KHR_vulkan_memory_model in dead-code elimination (#4320)
+    - Support folding OpBitcast with numeric constants (#4247)
+
+ - Fuzz
+    - Add tests for MaybeGet* functions in fuzzerutil (#4284)
+    - Fix OutlineFunction in presence of unreachable blocks (#4308)
+    - Fix def-use update in PermutePhiOperands (#4309)
+    - Swap positions of two functions in a module (#4236)
+
+v2021.1 2021-04-19
+ - General
+    - Support SPV_KHR_linkonce_odr, SPV_KHR_expect_assume (#4161)
+    - Fixes for the vscode language server extension (#4150)
+ - Validator
+    - Add validation for SPV_EXT_shader_atomic_float_min_max (#4105)
+    - Add Vulkan Execution Scope checks (#4183)
+    - Vulkan 64-bit OpAtomicStore check (#4163)
+ - Optimizer
+    - Add interpolate legalization pass (#4220)
+ - Fuzz
+    - Various performance optimizations
+    - Do not add too many dead blocks (#4217)
+    - Add WGSL compatibility flag to context (#4193)
+    - Add persistent state to the fuzzer (#4137)
+
+v2020.7 2021-02-16
+ - General
+    - Support pending Intel extensions (#4116)
+    - Remove WebGPU support (#4108)
+ - Validator
+    - Vulkan image gather constant component (#4133)
+    - Add Vulkan PSB64 convert VUID (#4122)
+    - Validate SPV_KHR_workgroup_memory_explicit_layout (#4128)
+    - Validate VK_KHR_zero_initialize_workgroup_memory (#4124)
+    - Add Vulkan image gather offset VUID (#4118)
+    - Label Vulkan atomic semantics VUIDs (#4120)
+    - Label VUID 04662 (#4123)
+    - Label VUID 04683 (#4121)
+    - Add Vulkan EXT builtins (#4115)
+    - Validate Sampled=1 for Vulkan ImageQuerySizeLod, ImageQueryLevels, ImageQueryLod (#4103)
+    - Add Vulkan Memory Scope VUs (#4106)
+    - Add Vulkan Addressing Model check (#4107)
+    - Vulkan atomic storage class (#4079)
+    - Label standalone Vulkan VUID (#4091)
+    - Add Vulkan decroation VUID (#4090)
+    - Add Vulkan FP Mode VUID (#4088)
+    - Fix Vulkan image sampled check (#4085)
+    - Add Vulkan ForwardPointer VUID (#4089)
+    - Add Vulkan ImageTexelPointer format check (#4087)
+    - Add Vulkan Group Operation VUID (#4086)
+    - Add first StandAlone VUID 04633 (#4077)
+    - Add Subgroup VUIDs (#4074)
+    - validate return type of OpImageRead (#4072)
+    - tighter validation of multisampled images (#4059)
+    - validate OpTypeImage Sampled values for environemnts (#4064)
+    - validate StorageImageMultisampled capability (#4062)
+    - Add last TessLevelOuter and TessLevelInner VUID (#4055)
+    - Add last ClipDistance and CullDistance VUID (#4054)
+    - Add last ViewportIndex and Layer VUID (#4053)
+    - Add last Position VUID (#4052)
+    - Allow forward pointer to be used in types generally (#4044)
+ - Optimizer
+    - Mark module as modified if convert-to-half removes decorations (#4127)
+    - Fix binding number calculation in desc sroa (#4095)
+    - Run DCE when SPV_KHR_shader_clock is used (#4049)
+ - Debug Info
+    - Set correct scope and line info for DebugValue (#4125)
+    - Avoid integrity check failures caused by propagating line instructions (#4096)
+ - Linker
+    - Linker usability improvements (#4084)
+ - Instrumentation
+    - Generate differentiated error codes for buffer oob checking (#4097)
+ - Fuzz
+    - Fix OpPhi handling in DuplicateRegionWithSelection (#4065)
 
 v2020.6 2020-12-07
  - General
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 55f84e6..70caf85 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,7 @@
 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
 set(CMAKE_CXX_STANDARD 11)
 
+option(ENABLE_RTTI "Enables RTTI" OFF)
 option(SPIRV_ALLOW_TIMERS "Allow timers via clock_gettime on supported platforms" ON)
 
 if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
@@ -48,6 +49,8 @@
   add_definitions(-DSPIRV_MAC)
 elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "iOS")
   add_definitions(-DSPIRV_IOS)
+elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "tvOS")
+  add_definitions(-DSPIRV_TVOS)
 elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Android")
   add_definitions(-DSPIRV_ANDROID)
   set(SPIRV_TIMER_ENABLED ${SPIRV_ALLOW_TIMERS})
@@ -81,6 +84,10 @@
 
 option(SPIRV_BUILD_FUZZER "Build spirv-fuzz" OFF)
 
+set(SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS "" CACHE STRING "Used by OSS-Fuzz to control, via link options, which fuzzing engine should be used")
+
+option(SPIRV_BUILD_LIBFUZZER_TARGETS "Build libFuzzer targets" OFF)
+
 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")))
   set(COMPILER_IS_LIKE_GNU TRUE)
@@ -157,6 +164,7 @@
 #   Note this target provides no API stability guarantees.
 #
 # Ideally, all of these will go away - see https://github.com/KhronosGroup/SPIRV-Tools/issues/3909.
+option(ENABLE_EXCEPTIONS_ON_MSVC "Build SPIRV-TOOLS with c++ exceptions enabled in MSVC" ON)
 option(SPIRV_TOOLS_BUILD_STATIC "Build ${SPIRV_TOOLS}-static target. ${SPIRV_TOOLS} will alias to ${SPIRV_TOOLS}-static or ${SPIRV_TOOLS}-shared based on BUILD_SHARED_LIBS" ON)
 if(SPIRV_TOOLS_BUILD_STATIC)
   set(SPIRV_TOOLS_FULL_VISIBILITY ${SPIRV_TOOLS}-static)
@@ -176,11 +184,14 @@
   target_compile_options(${TARGET} PRIVATE ${SPIRV_WARNINGS})
 
   if (${COMPILER_IS_LIKE_GNU})
-    target_compile_options(${TARGET} PRIVATE
-      -std=c++11 -fno-exceptions -fno-rtti)
+    target_compile_options(${TARGET} PRIVATE -std=c++11 -fno-exceptions)
     target_compile_options(${TARGET} PRIVATE
       -Wall -Wextra -Wno-long-long -Wshadow -Wundef -Wconversion
       -Wno-sign-conversion)
+
+    if(NOT ENABLE_RTTI)
+        add_compile_options(-fno-rtti)
+    endif()
     # For good call stacks in profiles, keep the frame pointers.
     if(NOT "${SPIRV_PERF}" STREQUAL "")
       target_compile_options(${TARGET} PRIVATE -fno-omit-frame-pointer)
@@ -205,7 +216,9 @@
   if (MSVC)
     # Specify /EHs for exception handling. This makes using SPIRV-Tools as
     # dependencies in other projects easier.
-    target_compile_options(${TARGET} PRIVATE /EHs)
+    if(ENABLE_EXCEPTIONS_ON_MSVC)
+      target_compile_options(${TARGET} PRIVATE /EHs)
+    endif()
   endif()
 
   # For MinGW cross compile, statically link to the C++ runtime.
@@ -286,7 +299,7 @@
 # Turn off if they take too long.
 option(SPIRV_CHECK_CONTEXT "In a debug build, check if the IR context is in a valid state." ON)
 if (${SPIRV_CHECK_CONTEXT})
-  add_definitions(-DSPIRV_CHECK_CONTEXT)
+  add_compile_options($<$<CONFIG:Debug>:-DSPIRV_CHECK_CONTEXT>)
 endif()
 
 # Precompiled header macro. Parameters are source file list and filename for pch cpp file.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b46ae31..1eb8b68 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -98,13 +98,6 @@
     scenarios? The respective SPIR-V dialects are slightly different.
 *   Changes are made to a container while iterating through it. You have to be
     careful that iterators are not invalidated or that elements are not skipped.
-*   C++11 and VS2013. We generally assume that we have a C++11 compliant
-    compiler. However, on Windows, we still support Visual Studio 2013, which is
-    not fully C++11 compliant. See
-    [here](https://msdn.microsoft.com/en-us/library/hh567368.aspx). In
-    particular, note that it does not provide default move-constructors or
-    move-assignments for classes. In general, r-value references do not work the
-    way you might assume they do.
 *   For SPIR-V transforms: The module is changed, but the analyses are not
     updated. For example, a new instruction is added, but the def-use manager is
     not updated. Later on, it is possible that the def-use manager will be used,
diff --git a/DEPS b/DEPS
index 3762681..bf76893 100644
--- a/DEPS
+++ b/DEPS
@@ -4,9 +4,9 @@
   'github': 'https://github.com',
 
   'effcee_revision': '2ec8f8738118cc483b67c04a759fee53496c5659',
-  'googletest_revision': '3af06fe1664d30f98de1e78c53a7087e842a2547',
-  're2_revision': 'ca11026a032ce2a3de4b3c389ee53d2bdc8794d6',
-  'spirv_headers_revision': 'f027d53ded7e230e008d37c8b47ede7cd308e19d',
+  'googletest_revision': '955c7f837efad184ec63e771c42542d37545eaef',
+  're2_revision': '4244cd1cb492fa1d10986ec67f862964c073f844',
+  'spirv_headers_revision': '19e8350415ed9516c8afffa19ae2c58559495a67',
 }
 
 deps = {
diff --git a/README.md b/README.md
index 3637305..5cb1b34 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,6 @@
 
 ## 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)
@@ -45,6 +44,20 @@
 Use the `--version` option on each command line tool to see the software
 version.  An API call reports the software version as a C-style string.
 
+## Releases
+
+Some versions of SPIRV-Tools are tagged as stable releases (see
+[tags](https://github.com/KhronosGroup/SPIRV-Tools/tags) on github).
+These versions undergo extra testing.
+Releases are not directly related to releases (or versions) of
+[SPIRV-Headers][spirv-headers].
+Releases of SPIRV-Tools are tested against the version of SPIRV-Headers listed
+in the [DEPS](DEPS) file.
+The release generally uses the most recent compatible version of SPIRV-Headers
+available at the time of release.
+No version of SPIRV-Headers other than the one listed in the DEPS file is
+guaranteed to work with the SPIRV-Tools release.
+
 ## Supported features
 
 ### Assembler, binary parser, and disassembler
@@ -242,6 +255,34 @@
 
 We intend to maintain a linear history on the GitHub `master` branch.
 
+### Getting the source
+
+Example of getting sources, assuming SPIRV-Tools is configured as a standalone project:
+
+    git clone https://github.com/KhronosGroup/SPIRV-Tools.git   spirv-tools
+    cd spirv-tools
+
+    # Check out sources for dependencies, at versions known to work together,
+    # as listed in the DEPS file.
+    python3 utils/git-sync-deps
+
+For some kinds of development, you may need the latest sources from the third-party projects:
+
+    git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-tools/external/spirv-headers
+    git clone https://github.com/google/googletest.git          spirv-tools/external/googletest
+    git clone https://github.com/google/effcee.git              spirv-tools/external/effcee
+    git clone https://github.com/google/re2.git                 spirv-tools/external/re2
+
+#### Dependency on Effcee
+
+Some tests depend on the [Effcee][effcee] library for stateful matching.
+Effcee itself depends on [RE2][re2].
+
+* If SPIRV-Tools is configured as part of a larger project that already uses
+  Effcee, then that project should include Effcee before SPIRV-Tools.
+* Otherwise, SPIRV-Tools expects Effcee sources to appear in `external/effcee`
+  and RE2 sources to appear in `external/re2`.
+
 ### Source code organization
 
 * `example`: demo code of using SPIRV-Tools APIs
@@ -260,14 +301,6 @@
 * `test/`: Tests, using the [googletest][googletest] framework
 * `tools/`: Command line executables
 
-Example of getting sources, assuming SPIRV-Tools is configured as a standalone project:
-
-    git clone https://github.com/KhronosGroup/SPIRV-Tools.git   spirv-tools
-    git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-tools/external/spirv-headers
-    git clone https://github.com/google/googletest.git          spirv-tools/external/googletest
-    git clone https://github.com/google/effcee.git              spirv-tools/external/effcee
-    git clone https://github.com/google/re2.git                 spirv-tools/external/re2
-
 ### Tests
 
 The project contains a number of tests, used to drive development
@@ -281,46 +314,12 @@
   `googletest` source into the `<spirv-dir>/external/googletest` directory before
   configuring and building the project.
 
-*Note*: You must use a version of googletest that includes
-[a fix][googletest-pull-612] for [googletest issue 610][googletest-issue-610].
-The fix is included on the googletest master branch any time after 2015-11-10.
-In particular, googletest must be newer than version 1.7.0.
-
-### Dependency on Effcee
-
-Some tests depend on the [Effcee][effcee] library for stateful matching.
-Effcee itself depends on [RE2][re2].
-
-* If SPIRV-Tools is configured as part of a larger project that already uses
-  Effcee, then that project should include Effcee before SPIRV-Tools.
-* Otherwise, SPIRV-Tools expects Effcee sources to appear in `external/effcee`
-  and RE2 sources to appear in `external/re2`.
-
-
 ## Build
 
-Instead of building manually, you can also download the binaries for your
-platform directly from the [master-tot release][master-tot-release] on GitHub.
-Those binaries are automatically uploaded by the buildbots after successful
-testing and they always reflect the current top of the tree of the master
-branch.
+*Note*: Prebuilt binaries are available from the [downloads](docs/downloads.md) page.
 
-In order to build the code, you first need to sync the external repositories
-that it depends on. Assume that `<spirv-dir>` is the root directory of the
-checked out code:
-
-```sh
-cd <spirv-dir>
-git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
-git clone https://github.com/google/effcee.git external/effcee
-git clone https://github.com/google/re2.git external/re2
-git clone https://github.com/google/googletest.git external/googletest # optional
-
-```
-
-*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.
+First [get the sources](#getting-the-source).
+Then build using CMake, Bazel, Android ndk-build, or the Emscripten SDK.
 
 ### Build using CMake
 You can build the project using [CMake][cmake]:
@@ -348,7 +347,7 @@
 
 ```sh
 # In <spirv-dir> (the SPIRV-Tools repo root):
-git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
+git clone --depth=1 --branch v3.13.0.1 https://github.com/protocolbuffers/protobuf external/protobuf
 
 # In your build directory:
 cmake [-G <platform-generator>] <spirv-dir> -DSPIRV_BUILD_FUZZER=ON
@@ -365,6 +364,30 @@
 cd <spirv-dir>
 bazel build :all
 ```
+### Build a node.js package using Emscripten
+
+The SPIRV-Tools core library can be built to a WebAssembly [node.js](https://nodejs.org)
+module. The resulting `SpirvTools` WebAssembly module only exports methods to
+assemble and disassemble SPIR-V modules.
+
+First, make sure you have the [Emscripten SDK](https://emscripten.org).
+Then:
+
+```sh
+cd <spirv-dir>
+./source/wasm/build.sh
+```
+
+The resulting node package, with JavaScript and TypeScript bindings, is
+written to `<spirv-dir>/out/web`.
+
+Note: This builds the package locally. It does *not* publish it to [npm](https://npmjs.org).
+
+To test the result:
+
+```sh
+node ./test/wasm/test.js
+```
 
 ### Tools you'll need
 
@@ -378,15 +401,17 @@
 - [Bazel](https://bazel.build/) (optional): if building the source with Bazel,
 you need to install Bazel Version 0.29.1 on your machine. Other versions may
 also work, but are not verified.
+- [Emscripten SDK](https://emscripten.org) (optional): if building the
+  WebAssembly module.
 
 SPIRV-Tools is regularly tested with the following compilers:
 
 On Linux
-- GCC version 4.8.5
-- Clang version 3.8
+- GCC version 9.3
+- Clang version 10.0
 
 On MacOS
-- AppleClang 10.0
+- AppleClang 11.0
 
 On Windows
 - Visual Studio 2015
@@ -421,7 +446,7 @@
 `/D_ITERATOR_DEBUG_LEVEL=0` on Windows, you can disable checked iterators and
 iterator debugging.
 
-### Android
+### Android ndk-build
 
 SPIR-V Tools supports building static libraries `libSPIRV-Tools.a` and
 `libSPIRV-Tools-opt.a` for Android:
@@ -442,7 +467,8 @@
 ```
 
 ### Updating DEPS
-Occasionally the entries in DEPS will need to be updated. This is done on demand
+
+Occasionally the entries in [DEPS](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
@@ -643,8 +669,32 @@
 
 ### Tests
 
-Tests are only built when googletest is found. Use `ctest` to run all the
-tests.
+Tests are only built when googletest is found.
+
+#### Running test with CMake
+
+Use `ctest -j <num threads>` to run all the tests. To run tests using all threads:
+```shell
+ctest -j$(nproc)
+```
+
+To run a single test target, use `ctest [-j <N>] -R <test regex>`. For example,
+you can run all `opt` tests with:
+```shell
+ctest -R 'spirv-tools-test_opt'
+```
+
+#### Running test with Bazel
+
+Use `bazel test :all` to run all tests. This will run tests in parallel by default.
+
+To run a single test target, specify `:my_test_target` instead of `:all`. Test target
+names get printed when you run `bazel test :all`. For example, you can run
+`opt_def_use_test` with:
+```shell
+bazel test :opt_def_use_test
+```
+
 
 ## Future Work
 <a name="future"></a>
@@ -705,4 +755,3 @@
 [CMake]: https://cmake.org/
 [cpp-style-guide]: https://google.github.io/styleguide/cppguide.html
 [clang-sanitizers]: http://clang.llvm.org/docs/UsersManual.html#controlling-code-generation
-[master-tot-release]: https://github.com/KhronosGroup/SPIRV-Tools/releases/tag/master-tot
diff --git a/build_defs.bzl b/build_defs.bzl
index 30af3bd..b2cd41b 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -41,6 +41,7 @@
 
 DEBUGINFO_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_debuginfo_grammar_unified1"
 CLDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_opencl_debuginfo_100_grammar_unified1"
+SHDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_nonsemantic_shader_debuginfo_100_grammar_unified1"
 
 def generate_core_tables(version = None):
     if not version:
@@ -201,6 +202,23 @@
         ] + deps,
     )
 
+def lint_test(name, srcs, deps = []):
+    if name[-5:] != "_test":
+        name = name + "_test"
+    native.cc_test(
+        name = "lint_" + name,
+        srcs = srcs,
+        compatible_with = [],
+        copts = TEST_COPTS,
+        size = "large",
+        deps = [
+            ":spirv_tools_lint",
+            "@com_google_googletest//:gtest_main",
+            "@com_google_googletest//:gtest",
+            "@com_google_effcee//:effcee",
+        ] + deps,
+    )
+
 def link_test(name, srcs, deps = []):
     if name[-5:] != "_test":
         name = name + "_test"
diff --git a/build_overrides/build.gni b/build_overrides/build.gni
index 833fcd3..cf3d6b7 100644
--- a/build_overrides/build.gni
+++ b/build_overrides/build.gni
@@ -16,9 +16,6 @@
 # Chromium specific targets in a client project's GN file etc.
 build_with_chromium = false
 
-# Don't use Chromium's third_party/binutils.
-linux_use_bundled_binutils_override = false
-
 declare_args() {
   # Android 32-bit non-component, non-clang builds cannot have symbol_level=2
   # due to 4GiB file size limit, see https://crbug.com/648948.
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..fb6d114
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,10 @@
+version: "3"
+services:
+  build:
+    image: emscripten/emsdk:2.0.2
+    environment:
+      GITHUB_RUN_NUMBER: ${GITHUB_RUN_NUMBER:-}
+    working_dir: /app
+    command: ./source/wasm/build.sh
+    volumes:
+      - ./:/app
diff --git a/docs/downloads.md b/docs/downloads.md
index 9c7d856..168937a 100644
--- a/docs/downloads.md
+++ b/docs/downloads.md
@@ -1,14 +1,28 @@
 # Downloads
-Download the latest builds.
 
-## Release
+## Latest builds
+
+Download the latest builds of the [master](https://github.com/KhronosGroup/SPIRV-Tools/tree/master) branch.
+
+### Release build
 | 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
+### Debug build
 | 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) | |
+
+
+## Vulkan SDK
+
+SPIRV-Tools is published as part of the [LunarG Vulkan SDK](https://www.lunarg.com/vulkan-sdk/).
+The Vulkan SDK is updated approximately every six weeks.
+
+## Android NDK
+
+SPIRV-Tools host executables, and library sources are published as
+part of the [Android NDK](https://developer.android.com/ndk/downloads).
diff --git a/docs/spirv-fuzz.md b/docs/spirv-fuzz.md
index e5439e3..5716b35 100644
--- a/docs/spirv-fuzz.md
+++ b/docs/spirv-fuzz.md
@@ -53,7 +53,7 @@
 
 The `IsApplicable` method should have a comment in the header file describing the conditions for applicability in simple terms.  These conditions should be implemented in the body of this method in the `.cpp` file.
 
-In the case of `TransformationSetSelectionControl`, `IsApplicable` involves checking that `block_id` is indeed the id of a block that has an `OpSelectoinMerge` instruction, and that `selection_control` is a valid selection mask.
+In the case of `TransformationSetSelectionControl`, `IsApplicable` involves checking that `block_id` is indeed the id of a block that has an `OpSelectionMerge` instruction, and that `selection_control` is a valid selection mask.
 
 The `Apply` method should have a comment in the header file summarising the result of applying the transformation.  It should be implemented in the `.cpp` file, and you should assume that `IsApplicable` holds whenever `Apply` is invoked.
 
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index 2b47a56..a19491f 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -170,11 +170,15 @@
 static const int kInstErrorBindlessBounds = 0;
 static const int kInstErrorBindlessUninit = 1;
 static const int kInstErrorBuffAddrUnallocRef = 2;
-static const int kInstErrorBindlessBuffOOB = 3;
+// Deleted: static const int kInstErrorBindlessBuffOOB = 3;
+// This comment will will remain for 2 releases to allow
+// for the transition of all builds. Buffer OOB is
+// generating the following four differentiated codes instead:
 static const int kInstErrorBuffOOBUniform = 4;
 static const int kInstErrorBuffOOBStorage = 5;
 static const int kInstErrorBuffOOBUniformTexel = 6;
 static const int kInstErrorBuffOOBStorageTexel = 7;
+static const int kInstErrorMax = kInstErrorBuffOOBStorageTexel;
 
 // Direct Input Buffer Offsets
 //
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 201ee58..8df14f5 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -113,6 +113,9 @@
 // Sometimes we also need to be able to express the fact that an operand
 // is a member of an optional tuple of values.  In that case the first member
 // would be optional, and the subsequent members would be required.
+//
+// NOTE: Although we don't promise binary compatibility, as a courtesy, please
+// add new enum values at the end.
 typedef enum spv_operand_type_t {
   // A sentinel value.
   SPV_OPERAND_TYPE_NONE = 0,
@@ -167,12 +170,8 @@
   SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS,              // SPIR-V Sec 3.29
   SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO,         // SPIR-V Sec 3.30
   SPV_OPERAND_TYPE_CAPABILITY,                    // SPIR-V Sec 3.31
-  SPV_OPERAND_TYPE_RAY_FLAGS,                     // SPIR-V Sec 3.RF
-  SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION,        // SPIR-V Sec 3.RQIntersection
-  SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE,  // SPIR-V Sec
-                                                           // 3.RQCommitted
-  SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE,  // SPIR-V Sec
-                                                           // 3.RQCandidate
+
+  // NOTE: New concrete enum values should be added at the end.
 
   // Set 5:  Operands that are a single word bitmask.
   // Sometimes a set bit indicates the instruction requires still more operands.
@@ -184,7 +183,10 @@
   SPV_OPERAND_TYPE_MEMORY_ACCESS,          // SPIR-V Sec 3.26
   SPV_OPERAND_TYPE_FRAGMENT_SHADING_RATE,  // SPIR-V Sec 3.FSR
 
-// The remaining operand types are only used internally by the assembler.
+// NOTE: New concrete enum values should be added at the end.
+
+// The "optional" and "variable"  operand types are only used internally by
+// the assembler and the binary parser.
 // There are two categories:
 //    Optional : expands to 0 or 1 operand, like ? in regular expressions.
 //    Variable : expands to 0, 1 or many operands or pairs of operands.
@@ -264,6 +266,24 @@
   // https://github.com/intel/llvm/blob/39fa9b0cbfbae88327118990a05c5b387b56d2ef/sycl/doc/extensions/SPIRV/SPV_INTEL_float_controls2.asciidoc
   SPV_OPERAND_TYPE_FPDENORM_MODE,     // Sec 3.17 FP Denorm Mode
   SPV_OPERAND_TYPE_FPOPERATION_MODE,  // Sec 3.18 FP Operation Mode
+  // A value enum from https://github.com/KhronosGroup/SPIRV-Headers/pull/177
+  SPV_OPERAND_TYPE_QUANTIZATION_MODES,
+  // A value enum from https://github.com/KhronosGroup/SPIRV-Headers/pull/177
+  SPV_OPERAND_TYPE_OVERFLOW_MODES,
+
+  // Concrete operand types for the provisional Vulkan ray tracing feature.
+  SPV_OPERAND_TYPE_RAY_FLAGS,               // SPIR-V Sec 3.RF
+  SPV_OPERAND_TYPE_RAY_QUERY_INTERSECTION,  // SPIR-V Sec 3.RQIntersection
+  SPV_OPERAND_TYPE_RAY_QUERY_COMMITTED_INTERSECTION_TYPE,  // SPIR-V Sec
+                                                           // 3.RQCommitted
+  SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE,  // SPIR-V Sec
+                                                           // 3.RQCandidate
+
+  // Concrete operand types for integer dot product.
+  // Packed vector format
+  SPV_OPERAND_TYPE_PACKED_VECTOR_FORMAT,  // SPIR-V Sec 3.x
+  // An optional packed vector format
+  SPV_OPERAND_TYPE_OPTIONAL_PACKED_VECTOR_FORMAT,
 
   // This is a sentinel value, and does not represent an operand type.
   // It should come last.
@@ -289,6 +309,7 @@
   SPV_EXT_INST_TYPE_DEBUGINFO,
   SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100,
   SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION,
+  SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100,
 
   // Multiple distinct extended instruction set types could return this
   // value, if they are prefixed with NonSemantic. and are otherwise
@@ -495,6 +516,7 @@
 
   SPV_ENV_UNIVERSAL_1_5,  // SPIR-V 1.5 latest revision, no other restrictions.
   SPV_ENV_VULKAN_1_2,     // Vulkan 1.2 latest revision.
+  SPV_ENV_MAX             // Keep this as the last enum value.
 } spv_target_env;
 
 // SPIR-V Validator can be parameterized with the following Universal Limits.
@@ -588,6 +610,8 @@
 // 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.
+// 4) GLSLstd450 Interpolate* instructions can have a load of an interpolant
+//    for a first argument.
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetBeforeHlslLegalization(
     spv_validator_options options, bool val);
 
@@ -625,11 +649,22 @@
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetScalarBlockLayout(
     spv_validator_options options, bool val);
 
+// Records whether the validator should use "scalar" block layout
+// rules (as defined above) for Workgroup blocks.  See Vulkan
+// extension VK_KHR_workgroup_memory_explicit_layout.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetWorkgroupScalarBlockLayout(
+    spv_validator_options options, bool val);
+
 // Records whether or not the validator should skip validating standard
 // uniform/storage block layout.
 SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetSkipBlockLayout(
     spv_validator_options options, bool val);
 
+// Records whether or not the validator should allow the LocalSizeId
+// decoration where the environment otherwise would not allow it.
+SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowLocalSizeId(
+    spv_validator_options options, bool val);
+
 // Creates an optimizer options object with default options. Returns a valid
 // options object. The object remains valid until it is passed into
 // |spvOptimizerOptionsDestroy|.
@@ -665,7 +700,7 @@
 // Creates a reducer options object with default options. Returns a valid
 // options object. The object remains valid until it is passed into
 // |spvReducerOptionsDestroy|.
-SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate();
+SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate(void);
 
 // Destroys the given reducer options object.
 SPIRV_TOOLS_EXPORT void spvReducerOptionsDestroy(spv_reducer_options options);
@@ -692,7 +727,7 @@
 // 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();
+SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate(void);
 
 // Destroys the given fuzzer options object.
 SPIRV_TOOLS_EXPORT void spvFuzzerOptionsDestroy(spv_fuzzer_options options);
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 2018e46..8dfb46b 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -104,11 +104,23 @@
     spvValidatorOptionsSetScalarBlockLayout(options_, val);
   }
 
+  // Enables scalar layout when validating Workgroup blocks.  See
+  // VK_KHR_workgroup_memory_explicit_layout.
+  void SetWorkgroupScalarBlockLayout(bool val) {
+    spvValidatorOptionsSetWorkgroupScalarBlockLayout(options_, val);
+  }
+
   // Skips validating standard uniform/storage buffer/push-constant layout.
   void SetSkipBlockLayout(bool val) {
     spvValidatorOptionsSetSkipBlockLayout(options_, val);
   }
 
+  // Enables LocalSizeId decorations where the environment would not otherwise
+  // allow them.
+  void SetAllowLocalSizeId(bool val) {
+    spvValidatorOptionsSetAllowLocalSizeId(options_, val);
+  }
+
   // Records whether or not the validator should relax the rules on pointer
   // usage in logical addressing mode.
   //
@@ -130,6 +142,8 @@
   // 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.
+  // 4) GLSLstd450 Interpolate* instructions can have a load of an interpolant
+  //    for a first argument.
   void SetBeforeHlslLegalization(bool val) {
     spvValidatorOptionsSetBeforeHlslLegalization(options_, val);
   }
diff --git a/include/spirv-tools/linter.hpp b/include/spirv-tools/linter.hpp
new file mode 100644
index 0000000..52ed5a4
--- /dev/null
+++ b/include/spirv-tools/linter.hpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef INCLUDE_SPIRV_TOOLS_LINTER_HPP_
+#define INCLUDE_SPIRV_TOOLS_LINTER_HPP_
+
+#include "libspirv.hpp"
+
+namespace spvtools {
+
+// C++ interface for SPIR-V linting functionalities. It wraps the context
+// (including target environment and the corresponding SPIR-V grammar) and
+// provides a method for linting.
+//
+// Instances of this class provides basic thread-safety guarantee.
+class Linter {
+ public:
+  explicit Linter(spv_target_env env);
+
+  ~Linter();
+
+  // 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);
+
+  // Returns a reference to the registered message consumer.
+  const MessageConsumer& Consumer() const;
+
+  bool Run(const uint32_t* binary, size_t binary_size);
+
+ private:
+  struct Impl;
+  std::unique_ptr<Impl> impl_;
+};
+}  // namespace spvtools
+
+#endif  // INCLUDE_SPIRV_TOOLS_LINTER_HPP_
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 1683d07..21059cb 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -19,6 +19,7 @@
 #include <ostream>
 #include <string>
 #include <unordered_map>
+#include <utility>
 #include <vector>
 
 #include "libspirv.hpp"
@@ -27,7 +28,8 @@
 
 namespace opt {
 class Pass;
-}
+struct DescriptorSetAndBinding;
+}  // namespace opt
 
 // C++ interface for SPIR-V optimization functionalities. It wraps the context
 // (including target environment and the corresponding SPIR-V grammar) and
@@ -512,7 +514,19 @@
 // Conversion, which tends to cause cycles of dead code to be left after
 // Store/Load elimination passes are completed. These cycles cannot be
 // eliminated with standard dead code elimination.
-Optimizer::PassToken CreateAggressiveDCEPass();
+//
+// If |preserve_interface| is true, all non-io variables in the entry point
+// interface are considered live and are not eliminated. This mode is needed
+// by GPU-Assisted validation instrumentation, where a change in the interface
+// is not allowed.
+Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface = false);
+
+// Creates a remove-unused-interface-variables pass.
+// Removes variables referenced on the |OpEntryPoint| instruction that are not
+// referenced in the entry point function or any function in its call tree. Note
+// that this could cause the shader interface to no longer match other shader
+// stages.
+Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass();
 
 // Creates an empty pass.
 // This is deprecated and will be removed.
@@ -692,8 +706,11 @@
 // Create a pass to reduce the size of loads.
 // This pass looks for loads of structures where only a few of its members are
 // used.  It replaces the loads feeding an OpExtract with an OpAccessChain and
-// a load of the specific elements.
-Optimizer::PassToken CreateReduceLoadSizePass();
+// a load of the specific elements.  The parameter is a threshold to determine
+// whether we have to replace the load or not.  If the ratio of the used
+// components of the load is less than the threshold, we replace the load.
+Optimizer::PassToken CreateReduceLoadSizePass(
+    double load_replacement_threshold = 0.9);
 
 // Create a pass to combine chained access chains.
 // This pass looks for access chains fed by other access chains and combines
@@ -816,6 +833,13 @@
 //   inclusive.
 Optimizer::PassToken CreateGraphicsRobustAccessPass();
 
+// Create a pass to replace a descriptor access using variable index.
+// This pass replaces every access using a variable index to array variable
+// |desc| that has a DescriptorSet and Binding decorations with a constant
+// element of the array. In order to replace the access using a variable index
+// with the constant element, it uses a switch statement.
+Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass();
+
 // Create descriptor scalar replacement pass.
 // This pass replaces every array variable |desc| that has a DescriptorSet and
 // Binding decorations with a new variable for each element of the array.
@@ -838,6 +862,25 @@
 // capabilities.
 Optimizer::PassToken CreateAmdExtToKhrPass();
 
+// Replaces the internal version of GLSLstd450 InterpolateAt* extended
+// instructions with the externally valid version. The internal version allows
+// an OpLoad of the interpolant for the first argument. This pass removes the
+// OpLoad and replaces it with its pointer. glslang and possibly other
+// frontends will create the internal version for HLSL. This pass will be part
+// of HLSL legalization and should be called after interpolants have been
+// propagated into their final positions.
+Optimizer::PassToken CreateInterpolateFixupPass();
+
+// Creates a convert-to-sampled-image pass to convert images and/or
+// samplers with given pairs of descriptor set and binding to sampled image.
+// If a pair of an image and a sampler have the same pair of descriptor set and
+// binding that is one of the given pairs, they will be converted to a sampled
+// image. In addition, if only an image has the descriptor set and binding that
+// is one of the given pairs, it will be converted to a sampled image as well.
+Optimizer::PassToken CreateConvertToSampledImagePass(
+    const std::vector<opt::DescriptorSetAndBinding>&
+        descriptor_set_binding_pairs);
+
 }  // namespace spvtools
 
 #endif  // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/kokoro/check-format/build.sh b/kokoro/check-format/build.sh
index 0c4b8d1..8a5df9a 100644
--- a/kokoro/check-format/build.sh
+++ b/kokoro/check-format/build.sh
@@ -35,7 +35,8 @@
 cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd ..
 git clone --depth=1 https://github.com/google/effcee              external/effcee
 git clone --depth=1 https://github.com/google/re2                 external/re2
-curl -L http://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/clang-format-diff.py -o utils/clang-format-diff.py;
+# The --fail flag causes the command to fail on HTTP error response codes, like 404.
+curl -L --fail https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o utils/clang-format-diff.py
 
 echo $(date): Check formatting...
 ./utils/check_code_format.sh;
diff --git a/kokoro/windows-msvc-2013-release/continuous.cfg b/kokoro/linux-clang-ubsan/build.sh
old mode 100644
new mode 100755
similarity index 63%
copy from kokoro/windows-msvc-2013-release/continuous.cfg
copy to kokoro/linux-clang-ubsan/build.sh
index 5dfcba6..b5941e3
--- a/kokoro/windows-msvc-2013-release/continuous.cfg
+++ b/kokoro/linux-clang-ubsan/build.sh
@@ -1,16 +1,24 @@
-# Copyright (c) 2018 Google LLC.
+#!/bin/bash
+# Copyright (c) 2021 Google LLC.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#      http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT 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.
 
-# Continuous build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2013-release/build.bat"
+# Fail on any error.
+set -e
+# Display commands being run.
+set -x
+
+SCRIPT_DIR=`dirname "$BASH_SOURCE"`
+source $SCRIPT_DIR/../scripts/linux/build.sh UBSAN clang cmake
diff --git a/kokoro/windows-msvc-2013-release/continuous.cfg b/kokoro/linux-clang-ubsan/continuous.cfg
similarity index 84%
rename from kokoro/windows-msvc-2013-release/continuous.cfg
rename to kokoro/linux-clang-ubsan/continuous.cfg
index 5dfcba6..cb5535e 100644
--- a/kokoro/windows-msvc-2013-release/continuous.cfg
+++ b/kokoro/linux-clang-ubsan/continuous.cfg
@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Google LLC.
+# Copyright (c) 2021 Google LLC.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,4 +13,4 @@
 # limitations under the License.
 
 # Continuous build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2013-release/build.bat"
+build_file: "SPIRV-Tools/kokoro/linux-clang-ubsan/build.sh"
diff --git a/kokoro/windows-msvc-2013-release/continuous.cfg b/kokoro/linux-clang-ubsan/presubmit.cfg
similarity index 79%
copy from kokoro/windows-msvc-2013-release/continuous.cfg
copy to kokoro/linux-clang-ubsan/presubmit.cfg
index 5dfcba6..029c74a 100644
--- a/kokoro/windows-msvc-2013-release/continuous.cfg
+++ b/kokoro/linux-clang-ubsan/presubmit.cfg
@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Google LLC.
+# Copyright (c) 2021 Google LLC.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,5 +12,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Continuous build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2013-release/build.bat"
+# Presubmit build configuration.
+build_file: "SPIRV-Tools/kokoro/linux-clang-ubsan/build.sh"
diff --git a/kokoro/scripts/linux/build-docker.sh b/kokoro/scripts/linux/build-docker.sh
index ba21698..8f76803 100755
--- a/kokoro/scripts/linux/build-docker.sh
+++ b/kokoro/scripts/linux/build-docker.sh
@@ -51,14 +51,14 @@
 pushd external/googletest; git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7; popd
 clone_if_missing https://github.com/google/effcee              external/effcee        --depth=1
 clone_if_missing https://github.com/google/re2                 external/re2           --depth=1
-clone_if_missing https://github.com/protocolbuffers/protobuf   external/protobuf      --branch v3.13.0
+clone_if_missing https://github.com/protocolbuffers/protobuf   external/protobuf      --branch v3.13.0.1
 
 if [ $TOOL = "cmake" ]; then
   using cmake-3.17.2
   using ninja-1.10.0
 
   # Possible configurations are:
-  # ASAN, COVERAGE, RELEASE, DEBUG, DEBUG_EXCEPTION, RELEASE_MINGW
+  # ASAN, UBSAN, COVERAGE, RELEASE, DEBUG, DEBUG_EXCEPTION, RELEASE_MINGW
   BUILD_TYPE="Debug"
   if [ $CONFIG = "RELEASE" ] || [ $CONFIG = "RELEASE_MINGW" ]; then
     BUILD_TYPE="RelWithDebInfo"
@@ -69,6 +69,13 @@
   if [ $CONFIG = "ASAN" ]; then
     ADDITIONAL_CMAKE_FLAGS="-DSPIRV_USE_SANITIZER=address,bounds,null"
     [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; }
+  elif [ $CONFIG = "UBSAN" ]; then
+    # UBSan requires RTTI, and by default UBSan does not exit when errors are
+    # encountered - additional compiler options are required to force this.
+    # The -DSPIRV_USE_SANITIZER=undefined option instructs SPIR-V Tools to be
+    # built with UBSan enabled.
+    ADDITIONAL_CMAKE_FLAGS="-DSPIRV_USE_SANITIZER=undefined -DENABLE_RTTI=ON -DCMAKE_C_FLAGS=-fno-sanitize-recover=all -DCMAKE_CXX_FLAGS=-fno-sanitize-recover=all"
+    [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; }
   elif [ $CONFIG = "COVERAGE" ]; then
     ADDITIONAL_CMAKE_FLAGS="-DENABLE_CODE_COVERAGE=ON"
     SKIP_TESTS="True"
@@ -79,6 +86,10 @@
     SKIP_TESTS="True"
   fi
 
+  if [ $COMPILER = "clang" ]; then
+    ADDITIONAL_CMAKE_FLAGS="$ADDITIONAL_CMAKE_FLAGS -DSPIRV_BUILD_LIBFUZZER_TARGETS=ON"
+  fi
+
   clean_dir "$ROOT_DIR/build"
   cd "$ROOT_DIR/build"
 
diff --git a/kokoro/scripts/macos/build.sh b/kokoro/scripts/macos/build.sh
index 44c9a41..4612823 100644
--- a/kokoro/scripts/macos/build.sh
+++ b/kokoro/scripts/macos/build.sh
@@ -36,7 +36,7 @@
 cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd ..
 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 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
+git clone --depth=1 --branch v3.13.0.1 https://github.com/protocolbuffers/protobuf external/protobuf
 
 mkdir build && cd $SRC/build
 
diff --git a/kokoro/scripts/windows/build.bat b/kokoro/scripts/windows/build.bat
index fa7a71a..24e29cc 100644
--- a/kokoro/scripts/windows/build.bat
+++ b/kokoro/scripts/windows/build.bat
@@ -22,7 +22,7 @@
 set VS_VERSION=%2
 
 :: Force usage of python 3.6
-set PATH=C:\python36;%PATH%
+set PATH=C:\python36;"C:\Program Files\CMake\bin";%PATH%
 
 cd %SRC%
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
@@ -30,7 +30,7 @@
 cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd ..
 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 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
+git clone --depth=1 --branch v3.13.0.1 https://github.com/protocolbuffers/protobuf external/protobuf
 
 :: #########################################
 :: set up msvc build env
@@ -41,9 +41,6 @@
 ) else if %VS_VERSION% == 2015 (
   call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x64
   echo "Using VS 2015..."
-) else if %VS_VERSION% == 2013 (
-  call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x64
-  echo "Using VS 2013..."
 )
 
 cd %SRC%
@@ -62,15 +59,8 @@
 
 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
-)
+:: Build spirv-fuzz
+set CMAKE_FLAGS=%CMAKE_FLAGS% -DSPIRV_BUILD_FUZZER=ON
 
 cmake %CMAKE_FLAGS% ..
 
@@ -85,13 +75,11 @@
 setlocal ENABLEDELAYEDEXPANSION
 
 :: ################################################
-:: Run the tests (We no longer run tests on VS2013)
+:: Run the tests
 :: ################################################
 echo "Running Tests... %DATE% %TIME%"
-if %VS_VERSION% NEQ 2013 (
-  ctest -C %BUILD_TYPE% --output-on-failure --timeout 300
-  if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
-)
+ctest -C %BUILD_TYPE% --output-on-failure --timeout 300
+if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
 echo "Tests Completed %DATE% %TIME%"
 
 :: ################################################
@@ -106,4 +94,3 @@
 rm -rf %SRC%\external
 
 exit /b 0
-
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 65087f2..331ff67 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -17,10 +17,10 @@
 set(XML_REGISTRY_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_registry_tables.py")
 set(LANG_HEADER_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_language_headers.py")
 
-# For now, assume the DebugInfo grammar file is in the current directory.
-# It might migrate to SPIRV-Headers.
+# Pull in grammar files that have migrated to SPIRV-Headers
 set(DEBUGINFO_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.debuginfo.grammar.json")
 set(CLDEBUGINFO100_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json")
+set(VKDEBUGINFO100_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json")
 
 # macro() definitions are used in the following because we need to append .inc
 # file paths into some global lists (*_CPP_DEPENDS). And those global lists are
@@ -113,6 +113,9 @@
 macro(spvtools_vendor_tables VENDOR_TABLE SHORT_NAME OPERAND_KIND_PREFIX)
   set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc")
   set(GRAMMAR_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.${VENDOR_TABLE}.grammar.json")
+  if(NOT EXISTS ${GRAMMAR_FILE})
+    set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json")
+  endif()
   add_custom_command(OUTPUT ${INSTS_FILE}
     COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT}
       --extinst-vendor-grammar=${GRAMMAR_FILE}
@@ -148,9 +151,11 @@
 spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb" "")
 spvtools_vendor_tables("debuginfo" "debuginfo" "")
 spvtools_vendor_tables("opencl.debuginfo.100" "cldi100" "CLDEBUG100_")
+spvtools_vendor_tables("nonsemantic.shader.debuginfo.100" "shdi100" "SHDEBUG100_")
 spvtools_vendor_tables("nonsemantic.clspvreflection" "clspvreflection" "")
 spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE})
 spvtools_extinst_lang_headers("OpenCLDebugInfo100" ${CLDEBUGINFO100_GRAMMAR_JSON_FILE})
+spvtools_extinst_lang_headers("NonSemanticShaderDebugInfo100" ${VKDEBUGINFO100_GRAMMAR_JSON_FILE})
 
 spvtools_vimsyntax("unified1" "1.0")
 add_custom_target(spirv-tools-vimsyntax DEPENDS ${VIMSYNTAX_FILE})
@@ -211,6 +216,7 @@
 add_subdirectory(reduce)
 add_subdirectory(fuzz)
 add_subdirectory(link)
+add_subdirectory(lint)
 
 set(SPIRV_SOURCES
   ${spirv-tools_SOURCE_DIR}/include/spirv-tools/libspirv.h
@@ -226,6 +232,7 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/assembly_grammar.h
   ${CMAKE_CURRENT_SOURCE_DIR}/binary.h
   ${CMAKE_CURRENT_SOURCE_DIR}/cfa.h
+  ${CMAKE_CURRENT_SOURCE_DIR}/common_debug_info.h
   ${CMAKE_CURRENT_SOURCE_DIR}/diagnostic.h
   ${CMAKE_CURRENT_SOURCE_DIR}/disassemble.h
   ${CMAKE_CURRENT_SOURCE_DIR}/enum_set.h
@@ -359,7 +366,7 @@
   )
   set_property(TARGET ${target} PROPERTY FOLDER "SPIRV-Tools libraries")
   spvtools_check_symbol_exports(${target})
-  add_dependencies(${target} core_tables enum_string_mapping extinst_tables)
+  add_dependencies(${target} spirv-tools-build-version core_tables enum_string_mapping extinst_tables)
 endfunction()
 
 # Always build ${SPIRV_TOOLS}-shared. This is expected distro packages, and
@@ -402,6 +409,12 @@
   endif()
 endif()
 
+if (ANDROID)
+    foreach(target ${SPIRV_TOOLS_TARGETS})
+        target_link_libraries(${target} PRIVATE android log)
+    endforeach()
+endif()
+
 if(ENABLE_SPIRV_TOOLS_INSTALL)
   install(TARGETS ${SPIRV_TOOLS_TARGETS} EXPORT ${SPIRV_TOOLS}Targets
     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -415,8 +428,10 @@
   # Special config file for root library compared to other libs.
   file(WRITE ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake
     "include(\${CMAKE_CURRENT_LIST_DIR}/${SPIRV_TOOLS}Target.cmake)\n"
-    "set(${SPIRV_TOOLS}_LIBRARIES ${SPIRV_TOOLS})\n"
-    "get_target_property(${SPIRV_TOOLS}_INCLUDE_DIRS ${SPIRV_TOOLS} INTERFACE_INCLUDE_DIRECTORIES)\n")
+    "if(TARGET ${SPIRV_TOOLS})\n"
+    "    set(${SPIRV_TOOLS}_LIBRARIES ${SPIRV_TOOLS})\n"
+    "    get_target_property(${SPIRV_TOOLS}_INCLUDE_DIRS ${SPIRV_TOOLS} INTERFACE_INCLUDE_DIRECTORIES)\n"
+    "endif()\n")
   install(FILES ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake DESTINATION ${PACKAGE_DIR})
 endif(ENABLE_SPIRV_TOOLS_INSTALL)
 
diff --git a/source/binary.cpp b/source/binary.cpp
index 7448721..93d5da7 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -507,7 +507,8 @@
 
     case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
       assert(SpvOpSpecConstantOp == opcode);
-      if (grammar_.lookupSpecConstantOpcode(SpvOp(word))) {
+      if (word > static_cast<uint32_t>(SpvOp::SpvOpMax) ||
+          grammar_.lookupSpecConstantOpcode(SpvOp(word))) {
         return diagnostic()
                << "Invalid " << spvOperandTypeStr(type) << ": " << word;
       }
@@ -657,12 +658,18 @@
     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
     case SPV_OPERAND_TYPE_FPDENORM_MODE:
-    case SPV_OPERAND_TYPE_FPOPERATION_MODE: {
+    case SPV_OPERAND_TYPE_FPOPERATION_MODE:
+    case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
+    case SPV_OPERAND_TYPE_OVERFLOW_MODES:
+    case SPV_OPERAND_TYPE_PACKED_VECTOR_FORMAT:
+    case SPV_OPERAND_TYPE_OPTIONAL_PACKED_VECTOR_FORMAT: {
       // A single word that is a plain enum value.
 
       // Map an optional operand type to its corresponding concrete type.
       if (type == SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER)
         parsed_operand.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER;
+      if (type == SPV_OPERAND_TYPE_OPTIONAL_PACKED_VECTOR_FORMAT)
+        parsed_operand.type = SPV_OPERAND_TYPE_PACKED_VECTOR_FORMAT;
 
       spv_operand_desc entry;
       if (grammar_.lookupOperand(type, word, &entry)) {
diff --git a/source/common_debug_info.h b/source/common_debug_info.h
new file mode 100644
index 0000000..ffa5d34
--- /dev/null
+++ b/source/common_debug_info.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2021 The Khronos Group Inc.
+// Copyright (c) 2021 Valve Corporation
+// Copyright (c) 2021 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_COMMON_DEBUG_INFO_HEADER_H_
+#define SOURCE_COMMON_DEBUG_INFO_HEADER_H_
+
+// This enum defines the known common set of instructions that are the same
+// between OpenCL.DebugInfo.100 and NonSemantic.Shader.DebugInfo.100.
+// Note that NonSemantic.Shader.* instructions can still have slightly
+// different encoding, as it does not use literals anywhere and only constants.
+enum CommonDebugInfoInstructions {
+  CommonDebugInfoDebugInfoNone = 0,
+  CommonDebugInfoDebugCompilationUnit = 1,
+  CommonDebugInfoDebugTypeBasic = 2,
+  CommonDebugInfoDebugTypePointer = 3,
+  CommonDebugInfoDebugTypeQualifier = 4,
+  CommonDebugInfoDebugTypeArray = 5,
+  CommonDebugInfoDebugTypeVector = 6,
+  CommonDebugInfoDebugTypedef = 7,
+  CommonDebugInfoDebugTypeFunction = 8,
+  CommonDebugInfoDebugTypeEnum = 9,
+  CommonDebugInfoDebugTypeComposite = 10,
+  CommonDebugInfoDebugTypeMember = 11,
+  CommonDebugInfoDebugTypeInheritance = 12,
+  CommonDebugInfoDebugTypePtrToMember = 13,
+  CommonDebugInfoDebugTypeTemplate = 14,
+  CommonDebugInfoDebugTypeTemplateParameter = 15,
+  CommonDebugInfoDebugTypeTemplateTemplateParameter = 16,
+  CommonDebugInfoDebugTypeTemplateParameterPack = 17,
+  CommonDebugInfoDebugGlobalVariable = 18,
+  CommonDebugInfoDebugFunctionDeclaration = 19,
+  CommonDebugInfoDebugFunction = 20,
+  CommonDebugInfoDebugLexicalBlock = 21,
+  CommonDebugInfoDebugLexicalBlockDiscriminator = 22,
+  CommonDebugInfoDebugScope = 23,
+  CommonDebugInfoDebugNoScope = 24,
+  CommonDebugInfoDebugInlinedAt = 25,
+  CommonDebugInfoDebugLocalVariable = 26,
+  CommonDebugInfoDebugInlinedVariable = 27,
+  CommonDebugInfoDebugDeclare = 28,
+  CommonDebugInfoDebugValue = 29,
+  CommonDebugInfoDebugOperation = 30,
+  CommonDebugInfoDebugExpression = 31,
+  CommonDebugInfoDebugMacroDef = 32,
+  CommonDebugInfoDebugMacroUndef = 33,
+  CommonDebugInfoDebugImportedEntity = 34,
+  CommonDebugInfoDebugSource = 35,
+  CommonDebugInfoInstructionsMax = 0x7ffffff
+};
+
+#endif  // SOURCE_COMMON_DEBUG_INFO_HEADER_H_
diff --git a/source/diagnostic.cpp b/source/diagnostic.cpp
index edc27c8..f3aa259 100644
--- a/source/diagnostic.cpp
+++ b/source/diagnostic.cpp
@@ -37,7 +37,7 @@
   diagnostic->position = *position;
   diagnostic->isTextSource = false;
   memset(diagnostic->error, 0, length);
-  strncpy(diagnostic->error, message, length);
+  strcpy(diagnostic->error, message);
   return diagnostic;
 }
 
diff --git a/source/disassemble.cpp b/source/disassemble.cpp
index 966a59c..c553988 100644
--- a/source/disassemble.cpp
+++ b/source/disassemble.cpp
@@ -328,7 +328,9 @@
     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION:
     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
     case SPV_OPERAND_TYPE_FPDENORM_MODE:
-    case SPV_OPERAND_TYPE_FPOPERATION_MODE: {
+    case SPV_OPERAND_TYPE_FPOPERATION_MODE:
+    case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
+    case SPV_OPERAND_TYPE_OVERFLOW_MODES: {
       spv_operand_desc entry;
       if (grammar_.lookupOperand(operand.type, word, &entry))
         assert(false && "should have caught this earlier");
@@ -345,7 +347,17 @@
       EmitMaskOperand(operand.type, word);
       break;
     default:
-      assert(false && "unhandled or invalid case");
+      if (spvOperandIsConcreteMask(operand.type)) {
+        EmitMaskOperand(operand.type, word);
+      } else if (spvOperandIsConcrete(operand.type)) {
+        spv_operand_desc entry;
+        if (grammar_.lookupOperand(operand.type, word, &entry))
+          assert(false && "should have caught this earlier");
+        stream_ << entry->name;
+      } else {
+        assert(false && "unhandled or invalid case");
+      }
+      break;
   }
   ResetColor();
 }
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index 795cb0f..812053e 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -29,6 +29,7 @@
 #include "debuginfo.insts.inc"
 #include "glsl.std.450.insts.inc"
 #include "nonsemantic.clspvreflection.insts.inc"
+#include "nonsemantic.shader.debuginfo.100.insts.inc"
 #include "opencl.debuginfo.100.insts.inc"
 #include "opencl.std.insts.inc"
 
@@ -55,6 +56,9 @@
      debuginfo_entries},
     {SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100,
      ARRAY_SIZE(opencl_debuginfo_100_entries), opencl_debuginfo_100_entries},
+    {SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100,
+     ARRAY_SIZE(nonsemantic_shader_debuginfo_100_entries),
+     nonsemantic_shader_debuginfo_100_entries},
     {SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION,
      ARRAY_SIZE(nonsemantic_clspvreflection_entries),
      nonsemantic_clspvreflection_entries},
@@ -126,6 +130,9 @@
   if (!strcmp("OpenCL.DebugInfo.100", name)) {
     return SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100;
   }
+  if (!strcmp("NonSemantic.Shader.DebugInfo.100", name)) {
+    return SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100;
+  }
   if (!strncmp("NonSemantic.ClspvReflection.", name, 28)) {
     return SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION;
   }
@@ -139,6 +146,7 @@
 
 bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) {
   if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN ||
+      type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100 ||
       type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) {
     return true;
   }
@@ -147,6 +155,7 @@
 
 bool spvExtInstIsDebugInfo(const spv_ext_inst_type_t type) {
   if (type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
+      type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100 ||
       type == SPV_EXT_INST_TYPE_DEBUGINFO) {
     return true;
   }
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index d3aa9f1..dd674dd 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -37,6 +37,7 @@
 
   set(SPIRV_TOOLS_FUZZ_SOURCES
         added_function_reducer.h
+        available_instructions.h
         call_graph.h
         comparator_deep_blocks_first.h
         counter_overflow_id_source.h
@@ -100,6 +101,7 @@
         fuzzer_pass_outline_functions.h
         fuzzer_pass_permute_blocks.h
         fuzzer_pass_permute_function_parameters.h
+        fuzzer_pass_permute_function_variables.h
         fuzzer_pass_permute_instructions.h
         fuzzer_pass_permute_phi_operands.h
         fuzzer_pass_propagate_instructions_down.h
@@ -119,8 +121,10 @@
         fuzzer_pass_split_blocks.h
         fuzzer_pass_swap_commutable_operands.h
         fuzzer_pass_swap_conditional_branch_operands.h
+        fuzzer_pass_swap_functions.h
         fuzzer_pass_toggle_access_chain_instruction.h
         fuzzer_pass_wrap_regions_in_selections.h
+        fuzzer_pass_wrap_vector_synonym.h
         fuzzer_util.h
         id_use_descriptor.h
         instruction_descriptor.h
@@ -221,14 +225,18 @@
         transformation_store.h
         transformation_swap_commutable_operands.h
         transformation_swap_conditional_branch_operands.h
+        transformation_swap_function_variables.h
+        transformation_swap_two_functions.h
         transformation_toggle_access_chain_instruction.h
         transformation_vector_shuffle.h
         transformation_wrap_early_terminator_in_function.h
         transformation_wrap_region_in_selection.h
+        transformation_wrap_vector_synonym.h
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
         added_function_reducer.cpp
+        available_instructions.cpp
         call_graph.cpp
         counter_overflow_id_source.cpp
         data_descriptor.cpp
@@ -290,6 +298,7 @@
         fuzzer_pass_outline_functions.cpp
         fuzzer_pass_permute_blocks.cpp
         fuzzer_pass_permute_function_parameters.cpp
+        fuzzer_pass_permute_function_variables.cpp
         fuzzer_pass_permute_instructions.cpp
         fuzzer_pass_permute_phi_operands.cpp
         fuzzer_pass_propagate_instructions_down.cpp
@@ -309,8 +318,10 @@
         fuzzer_pass_split_blocks.cpp
         fuzzer_pass_swap_commutable_operands.cpp
         fuzzer_pass_swap_conditional_branch_operands.cpp
+        fuzzer_pass_swap_functions.cpp
         fuzzer_pass_toggle_access_chain_instruction.cpp
         fuzzer_pass_wrap_regions_in_selections.cpp
+        fuzzer_pass_wrap_vector_synonym.cpp
         fuzzer_util.cpp
         id_use_descriptor.cpp
         instruction_descriptor.cpp
@@ -409,10 +420,13 @@
         transformation_store.cpp
         transformation_swap_commutable_operands.cpp
         transformation_swap_conditional_branch_operands.cpp
+        transformation_swap_function_variables.cpp
+        transformation_swap_two_functions.cpp
         transformation_toggle_access_chain_instruction.cpp
         transformation_vector_shuffle.cpp
         transformation_wrap_early_terminator_in_function.cpp
         transformation_wrap_region_in_selection.cpp
+        transformation_wrap_vector_synonym.cpp
         uniform_buffer_element_descriptor.cpp
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
         )
diff --git a/source/fuzz/available_instructions.cpp b/source/fuzz/available_instructions.cpp
new file mode 100644
index 0000000..0db8b20
--- /dev/null
+++ b/source/fuzz/available_instructions.cpp
@@ -0,0 +1,191 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/available_instructions.h"
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+AvailableInstructions::AvailableInstructions(
+    opt::IRContext* ir_context,
+    const std::function<bool(opt::IRContext*, opt::Instruction*)>& predicate)
+    : ir_context_(ir_context) {
+  // Consider all global declarations
+  for (auto& global : ir_context->module()->types_values()) {
+    if (predicate(ir_context, &global)) {
+      available_globals_.push_back(&global);
+    }
+  }
+
+  // Consider every function
+  for (auto& function : *ir_context->module()) {
+    // Identify those function parameters that satisfy the predicate.
+    std::vector<opt::Instruction*> available_params_for_function;
+    function.ForEachParam(
+        [&predicate, ir_context,
+         &available_params_for_function](opt::Instruction* param) {
+          if (predicate(ir_context, param)) {
+            available_params_for_function.push_back(param);
+          }
+        });
+
+    // Consider every reachable block in the function.
+    auto dominator_analysis = ir_context->GetDominatorAnalysis(&function);
+    for (auto& block : function) {
+      if (!ir_context->IsReachable(block)) {
+        // The block is not reachable.
+        continue;
+      }
+      if (&block == &*function.begin()) {
+        // The function entry block is special: only the relevant globals and
+        // function parameters are available at its entry point.
+        num_available_at_block_entry_.insert(
+            {&block,
+             static_cast<uint32_t>(available_params_for_function.size() +
+                                   available_globals_.size())});
+      } else {
+        // |block| is not the entry block and is reachable, so it must have an
+        // immediate dominator. The number of instructions available on entry to
+        // |block| is thus the number of instructions available on entry to the
+        // immediate dominator + the number of instructions generated_by_block
+        // by the immediate dominator.
+        auto immediate_dominator =
+            dominator_analysis->ImmediateDominator(&block);
+        assert(immediate_dominator != nullptr &&
+               "The block is reachable so should have an immediate dominator.");
+        assert(generated_by_block_.count(immediate_dominator) != 0 &&
+               "Immediate dominator should have already been processed.");
+        assert(num_available_at_block_entry_.count(immediate_dominator) != 0 &&
+               "Immediate dominator should have already been processed.");
+        num_available_at_block_entry_.insert(
+            {&block,
+             static_cast<uint32_t>(
+                 generated_by_block_.at(immediate_dominator).size()) +
+                 num_available_at_block_entry_.at(immediate_dominator)});
+      }
+      // Now consider each instruction in the block.
+      std::vector<opt::Instruction*> generated_by_block;
+      for (auto& inst : block) {
+        assert(num_available_at_block_entry_.count(&block) != 0 &&
+               "Block should have already been processed.");
+        // The number of available instructions before |inst| is the number
+        // available at the start of the block + the number of relevant
+        // instructions generated by the block so far.
+        num_available_before_instruction_.insert(
+            {&inst, num_available_at_block_entry_.at(&block) +
+                        static_cast<uint32_t>(generated_by_block.size())});
+        if (predicate(ir_context, &inst)) {
+          // This instruction satisfies the predicate, so note that it is
+          // generated by |block|.
+          generated_by_block.push_back(&inst);
+        }
+      }
+      generated_by_block_.emplace(&block, std::move(generated_by_block));
+    }
+    available_params_.emplace(&function,
+                              std::move(available_params_for_function));
+  }
+}
+
+AvailableInstructions::AvailableBeforeInstruction
+AvailableInstructions::GetAvailableBeforeInstruction(
+    opt::Instruction* inst) const {
+  assert(num_available_before_instruction_.count(inst) != 0 &&
+         "Availability can only be queried for reachable instructions.");
+  return {*this, inst};
+}
+
+AvailableInstructions::AvailableBeforeInstruction::AvailableBeforeInstruction(
+    const AvailableInstructions& available_instructions, opt::Instruction* inst)
+    : available_instructions_(available_instructions), inst_(inst) {}
+
+uint32_t AvailableInstructions::AvailableBeforeInstruction::size() const {
+  return available_instructions_.num_available_before_instruction_.at(inst_);
+}
+
+bool AvailableInstructions::AvailableBeforeInstruction::empty() const {
+  return size() == 0;
+}
+
+opt::Instruction* AvailableInstructions::AvailableBeforeInstruction::operator[](
+    uint32_t index) const {
+  assert(index < size() && "Index out of bounds.");
+
+  // First, check the cache to see whether we can return the available
+  // instruction in constant time.
+  auto cached_result = index_cache.find(index);
+  if (cached_result != index_cache.end()) {
+    return cached_result->second;
+  }
+
+  // Next check whether the index falls into the global region.
+  if (index < available_instructions_.available_globals_.size()) {
+    auto result = available_instructions_.available_globals_[index];
+    index_cache.insert({index, result});
+    return result;
+  }
+
+  auto block = available_instructions_.ir_context_->get_instr_block(inst_);
+  auto function = block->GetParent();
+
+  // Next check whether the index falls into the available instructions that
+  // correspond to function parameters.
+  if (index <
+      available_instructions_.available_globals_.size() +
+          available_instructions_.available_params_.at(function).size()) {
+    auto result = available_instructions_.available_params_.at(
+        function)[index - available_instructions_.available_globals_.size()];
+    index_cache.insert({index, result});
+    return result;
+  }
+
+  auto dominator_analysis =
+      available_instructions_.ir_context_->GetDominatorAnalysis(function);
+
+  // Now the expensive part (which is why we have the cache): walk the dominator
+  // tree backwards starting from the block containing |inst_| until we get to
+  // the block in which the instruction corresponding to |index| exists.
+  for (auto* ancestor = block; true;
+       ancestor = dominator_analysis->ImmediateDominator(ancestor)) {
+    uint32_t num_available_at_ancestor_entry =
+        available_instructions_.num_available_at_block_entry_.at(ancestor);
+    if (index_cache.count(num_available_at_ancestor_entry) == 0) {
+      // This is the first time we have traversed this block, so we populate the
+      // cache with the index of each instruction, so that if a future index
+      // query relates to indices associated with this block we can return the
+      // result in constant time.
+      auto& generated_by_ancestor =
+          available_instructions_.generated_by_block_.at(ancestor);
+      for (uint32_t local_index = 0; local_index < generated_by_ancestor.size();
+           local_index++) {
+        index_cache.insert({num_available_at_ancestor_entry + local_index,
+                            generated_by_ancestor[local_index]});
+      }
+    }
+    if (index >= num_available_at_ancestor_entry) {
+      // This block contains the instruction we want, so by now it will be in
+      // the cache.
+      return index_cache.at(index);
+    }
+    assert(ancestor != &*function->begin() &&
+           "By construction we should find a block associated with the index.");
+  }
+
+  assert(false && "Unreachable.");
+  return nullptr;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/available_instructions.h b/source/fuzz/available_instructions.h
new file mode 100644
index 0000000..5c0b417
--- /dev/null
+++ b/source/fuzz/available_instructions.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_AVAILABLE_INSTRUCTIONS_H_
+#define SOURCE_FUZZ_AVAILABLE_INSTRUCTIONS_H_
+
+#include <unordered_map>
+#include <vector>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A class for allowing efficient querying of the instruction that satisfy a
+// particular predicate that are available before a given instruction.
+// Availability information is only computed for instructions in *reachable*
+// basic blocks.
+class AvailableInstructions {
+ public:
+  // The outer class captures availability information for a whole module, and
+  // each instance of this inner class captures availability for a particular
+  // instruction.
+  class AvailableBeforeInstruction {
+   public:
+    AvailableBeforeInstruction(
+        const AvailableInstructions& available_instructions,
+        opt::Instruction* inst);
+
+    // Returns the number of instructions that are available before the
+    // instruction associated with this class.
+    uint32_t size() const;
+
+    // Returns true if and only if |size()| is 0.
+    bool empty() const;
+
+    // Requires |index| < |size()|. Returns the ith available instruction.
+    opt::Instruction* operator[](uint32_t index) const;
+
+   private:
+    // A references to an instance of the outer class.
+    const AvailableInstructions& available_instructions_;
+
+    // The instruction for which availability information is captured.
+    opt::Instruction* inst_;
+
+    // A cache to improve the efficiency of the [] operator. The [] operator
+    // requires walking the instruction's dominator tree to find an instruction
+    // at a particular index, which is a linear time operation. By inserting all
+    // instructions that are traversed during this search into a cache, future
+    // lookups will take constant time unless they require traversing the
+    // dominator tree more deeply.
+    mutable std::unordered_map<uint32_t, opt::Instruction*> index_cache;
+  };
+
+  // Constructs availability instructions for |ir_context|, where instructions
+  // are only available if they satisfy |predicate|.
+  AvailableInstructions(
+      opt::IRContext* ir_context,
+      const std::function<bool(opt::IRContext*, opt::Instruction*)>& predicate);
+
+  // Yields instruction availability for |inst|.
+  AvailableBeforeInstruction GetAvailableBeforeInstruction(
+      opt::Instruction* inst) const;
+
+ private:
+  // The module in which all instructions are contained.
+  opt::IRContext* ir_context_;
+
+  // The global instructions that satisfy the predicate.
+  std::vector<opt::Instruction*> available_globals_;
+
+  // Per function, the parameters that satisfy the predicate.
+  std::unordered_map<opt::Function*, std::vector<opt::Instruction*>>
+      available_params_;
+
+  // The number of instructions that satisfy the predicate and that are
+  // available at the entry to a block. For the entry block of a function this
+  // is the number of available globals + the number of available function
+  // parameters. For any other block it is the number of available instructions
+  // for the blocks immediate dominator + the number of instructions generated
+  // by the immediate dominator.
+  std::unordered_map<opt::BasicBlock*, uint32_t> num_available_at_block_entry_;
+
+  // For each block this records those instructions in the block that satisfy
+  // the predicate.
+  std::unordered_map<opt::BasicBlock*, std::vector<opt::Instruction*>>
+      generated_by_block_;
+
+  // For each instruction this records how many instructions satisfying the
+  // predicate are available before the instruction.
+  std::unordered_map<opt::Instruction*, uint32_t>
+      num_available_before_instruction_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_AVAILABLE_INSTRUCTIONS_H_
diff --git a/source/fuzz/force_render_red.cpp b/source/fuzz/force_render_red.cpp
index fd0587a..3267487 100644
--- a/source/fuzz/force_render_red.cpp
+++ b/source/fuzz/force_render_red.cpp
@@ -19,12 +19,10 @@
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation_context.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
-#include "source/fuzz/uniform_buffer_element_descriptor.h"
 #include "source/opt/build_module.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/types.h"
 #include "source/util/make_unique.h"
-#include "tools/util/cli_consumer.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -160,8 +158,8 @@
     const spv_target_env& target_env, spv_validator_options validator_options,
     const std::vector<uint32_t>& binary_in,
     const spvtools::fuzz::protobufs::FactSequence& initial_facts,
+    const MessageConsumer& message_consumer,
     std::vector<uint32_t>* binary_out) {
-  auto message_consumer = spvtools::utils::CLIMessageConsumer;
   spvtools::SpirvTools tools(target_env);
   if (!tools.IsValid()) {
     message_consumer(SPV_MSG_ERROR, nullptr, {},
diff --git a/source/fuzz/force_render_red.h b/source/fuzz/force_render_red.h
index b51c72b..5b8eab1 100644
--- a/source/fuzz/force_render_red.h
+++ b/source/fuzz/force_render_red.h
@@ -41,7 +41,7 @@
     const spv_target_env& target_env, spv_validator_options validator_options,
     const std::vector<uint32_t>& binary_in,
     const spvtools::fuzz::protobufs::FactSequence& initial_facts,
-    std::vector<uint32_t>* binary_out);
+    const MessageConsumer& message_consumer, std::vector<uint32_t>* binary_out);
 
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 40da497..9838e64 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -17,9 +17,7 @@
 #include <cassert>
 #include <memory>
 #include <numeric>
-#include <sstream>
 
-#include "source/fuzz/fact_manager/fact_manager.h"
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_pass_add_access_chains.h"
 #include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h"
@@ -69,6 +67,7 @@
 #include "source/fuzz/fuzzer_pass_outline_functions.h"
 #include "source/fuzz/fuzzer_pass_permute_blocks.h"
 #include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
+#include "source/fuzz/fuzzer_pass_permute_function_variables.h"
 #include "source/fuzz/fuzzer_pass_permute_instructions.h"
 #include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
 #include "source/fuzz/fuzzer_pass_propagate_instructions_down.h"
@@ -88,12 +87,11 @@
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
 #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
 #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h"
+#include "source/fuzz/fuzzer_pass_swap_functions.h"
 #include "source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h"
 #include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h"
+#include "source/fuzz/fuzzer_pass_wrap_vector_synonym.h"
 #include "source/fuzz/pass_management/repeated_pass_manager.h"
-#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h"
-#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h"
-#include "source/fuzz/pass_management/repeated_pass_manager_simple.h"
 #include "source/fuzz/pass_management/repeated_pass_recommender_standard.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation_context.h"
@@ -104,35 +102,153 @@
 namespace spvtools {
 namespace fuzz {
 
-namespace {
-const uint32_t kIdBoundGap = 100;
-
-}  // namespace
-
-Fuzzer::Fuzzer(spv_target_env target_env, MessageConsumer consumer,
-               const std::vector<uint32_t>& binary_in,
-               const protobufs::FactSequence& initial_facts,
+Fuzzer::Fuzzer(std::unique_ptr<opt::IRContext> ir_context,
+               std::unique_ptr<TransformationContext> transformation_context,
+               std::unique_ptr<FuzzerContext> fuzzer_context,
+               MessageConsumer consumer,
                const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
-               std::unique_ptr<RandomGenerator> random_generator,
                bool enable_all_passes,
                RepeatedPassStrategy repeated_pass_strategy,
                bool validate_after_each_fuzzer_pass,
-               spv_validator_options validator_options)
-    : target_env_(target_env),
-      consumer_(std::move(consumer)),
-      binary_in_(binary_in),
-      initial_facts_(initial_facts),
-      donor_suppliers_(donor_suppliers),
-      random_generator_(std::move(random_generator)),
+               spv_validator_options validator_options,
+               bool ignore_inapplicable_transformations /* = true */)
+    : consumer_(std::move(consumer)),
       enable_all_passes_(enable_all_passes),
-      repeated_pass_strategy_(repeated_pass_strategy),
       validate_after_each_fuzzer_pass_(validate_after_each_fuzzer_pass),
       validator_options_(validator_options),
       num_repeated_passes_applied_(0),
-      ir_context_(nullptr),
-      fuzzer_context_(nullptr),
-      transformation_context_(nullptr),
-      transformation_sequence_out_() {}
+      is_valid_(true),
+      ir_context_(std::move(ir_context)),
+      transformation_context_(std::move(transformation_context)),
+      fuzzer_context_(std::move(fuzzer_context)),
+      transformation_sequence_out_(),
+      pass_instances_(),
+      repeated_pass_recommender_(nullptr),
+      repeated_pass_manager_(nullptr),
+      final_passes_(),
+      ignore_inapplicable_transformations_(
+          ignore_inapplicable_transformations) {
+  assert(ir_context_ && "IRContext is not initialized");
+  assert(fuzzer_context_ && "FuzzerContext is not initialized");
+  assert(transformation_context_ && "TransformationContext is not initialized");
+  assert(fuzzerutil::IsValidAndWellFormed(ir_context_.get(), validator_options_,
+                                          consumer_) &&
+         "IRContext is invalid");
+
+  // The following passes are likely to be very useful: many other passes
+  // introduce synonyms, irrelevant ids and constants that these passes can work
+  // with.  We thus enable them with high probability.
+  MaybeAddRepeatedPass<FuzzerPassObfuscateConstants>(90, &pass_instances_);
+  MaybeAddRepeatedPass<FuzzerPassApplyIdSynonyms>(90, &pass_instances_);
+  MaybeAddRepeatedPass<FuzzerPassReplaceIrrelevantIds>(90, &pass_instances_);
+
+  do {
+    // Each call to MaybeAddRepeatedPass randomly decides whether the given pass
+    // should be enabled, and adds an instance of the pass to |pass_instances|
+    // if it is enabled.
+    MaybeAddRepeatedPass<FuzzerPassAddAccessChains>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddBitInstructionSynonyms>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddCompositeExtract>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddCompositeInserts>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddCompositeTypes>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddCopyMemory>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddDeadBlocks>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddDeadBreaks>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddDeadContinues>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddEquationInstructions>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddFunctionCalls>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddGlobalVariables>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddImageSampleUnusedComponents>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddLoads>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddLocalVariables>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddLoopPreheaders>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddLoopsToCreateIntConstantSynonyms>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddOpPhiSynonyms>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddParameters>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddRelaxedDecorations>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddStores>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddSynonyms>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassAddVectorShuffleInstructions>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassConstructComposites>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassCopyObjects>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassDonateModules>(&pass_instances_,
+                                                  donor_suppliers);
+    MaybeAddRepeatedPass<FuzzerPassDuplicateRegionsWithSelections>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassExpandVectorReductions>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassFlattenConditionalBranches>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassInlineFunctions>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassInvertComparisonOperators>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassMakeVectorOperationsDynamic>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassMergeBlocks>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassMergeFunctionReturns>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassMutatePointers>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassOutlineFunctions>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassPermuteFunctionParameters>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassPermuteInstructions>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsDown>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsUp>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassPushIdsThroughVariables>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceBranchesFromDeadBlocksWithExits>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceLoadsStoresWithCopyMemories>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceParameterWithGlobal>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceLinearAlgebraInstructions>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceOpPhiIdsFromDeadPredecessors>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceOpSelectsWithConditionalBranches>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassReplaceParamsWithStruct>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassSplitBlocks>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassSwapBranchConditionalOperands>(
+        &pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassWrapRegionsInSelections>(&pass_instances_);
+    MaybeAddRepeatedPass<FuzzerPassWrapVectorSynonym>(&pass_instances_);
+    // There is a theoretical possibility that no pass instances were created
+    // until now; loop again if so.
+  } while (pass_instances_.GetPasses().empty());
+
+  repeated_pass_recommender_ = MakeUnique<RepeatedPassRecommenderStandard>(
+      &pass_instances_, fuzzer_context_.get());
+  repeated_pass_manager_ = RepeatedPassManager::Create(
+      repeated_pass_strategy, fuzzer_context_.get(), &pass_instances_,
+      repeated_pass_recommender_.get());
+
+  MaybeAddFinalPass<FuzzerPassAdjustBranchWeights>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassAdjustFunctionControls>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassAdjustLoopControls>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassAdjustMemoryOperandsMasks>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassAdjustSelectionControls>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassAddNoContractionDecorations>(&final_passes_);
+  if (!fuzzer_context_->IsWgslCompatible()) {
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/4214):
+    //  this is disabled temporarily due to some issues in the Tint compiler.
+    //  Enable it back when the issues are resolved.
+    MaybeAddFinalPass<FuzzerPassInterchangeSignednessOfIntegerOperands>(
+        &final_passes_);
+  }
+  MaybeAddFinalPass<FuzzerPassInterchangeZeroLikeConstants>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassPermuteFunctionVariables>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassPermutePhiOperands>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassSwapCommutableOperands>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassSwapFunctions>(&final_passes_);
+  MaybeAddFinalPass<FuzzerPassToggleAccessChainInstruction>(&final_passes_);
+}
 
 Fuzzer::~Fuzzer() = default;
 
@@ -144,7 +260,8 @@
       fuzzer_context_->ChoosePercentage(percentage_chance_of_adding_pass)) {
     pass_instances->SetPass(MakeUnique<FuzzerPassT>(
         ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(),
-        &transformation_sequence_out_, std::forward<Args>(extra_args)...));
+        &transformation_sequence_out_, ignore_inapplicable_transformations_,
+        std::forward<Args>(extra_args)...));
   }
 }
 
@@ -154,7 +271,8 @@
   if (enable_all_passes_ || fuzzer_context_->ChooseEven()) {
     passes->push_back(MakeUnique<FuzzerPassT>(
         ir_context_.get(), transformation_context_.get(), fuzzer_context_.get(),
-        &transformation_sequence_out_, std::forward<Args>(extra_args)...));
+        &transformation_sequence_out_, ignore_inapplicable_transformations_,
+        std::forward<Args>(extra_args)...));
   }
 }
 
@@ -165,240 +283,107 @@
                                           consumer_);
 }
 
-Fuzzer::FuzzerResult Fuzzer::Run() {
-  // Check compatibility between the library version being linked with and the
-  // header files being used.
-  GOOGLE_PROTOBUF_VERIFY_VERSION;
+opt::IRContext* Fuzzer::GetIRContext() { return ir_context_.get(); }
 
-  assert(ir_context_ == nullptr && fuzzer_context_ == nullptr &&
-         transformation_context_ == nullptr &&
-         transformation_sequence_out_.transformation_size() == 0 &&
-         "'Run' must not be invoked more than once.");
-
-  spvtools::SpirvTools tools(target_env_);
-  tools.SetMessageConsumer(consumer_);
-  if (!tools.IsValid()) {
-    consumer_(SPV_MSG_ERROR, nullptr, {},
-              "Failed to create SPIRV-Tools interface; stopping.");
-    return {Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface,
-            std::vector<uint32_t>(), protobufs::TransformationSequence()};
-  }
-
-  // Initial binary should be valid.
-  if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) {
-    consumer_(SPV_MSG_ERROR, nullptr, {},
-              "Initial binary is invalid; stopping.");
-    return {Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid,
-            std::vector<uint32_t>(), protobufs::TransformationSequence()};
-  }
-
-  // Build the module from the input binary.
-  ir_context_ =
-      BuildModule(target_env_, consumer_, binary_in_.data(), binary_in_.size());
-  assert(ir_context_);
-
-  // 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;
-  fuzzer_context_ =
-      MakeUnique<FuzzerContext>(random_generator_.get(), minimum_fresh_id);
-
-  transformation_context_ = MakeUnique<TransformationContext>(
-      MakeUnique<FactManager>(ir_context_.get()), validator_options_);
-  transformation_context_->GetFactManager()->AddInitialFacts(consumer_,
-                                                             initial_facts_);
-
-  RepeatedPassInstances pass_instances{};
-
-  // The following passes are likely to be very useful: many other passes
-  // introduce synonyms, irrelevant ids and constants that these passes can work
-  // with.  We thus enable them with high probability.
-  MaybeAddRepeatedPass<FuzzerPassObfuscateConstants>(90, &pass_instances);
-  MaybeAddRepeatedPass<FuzzerPassApplyIdSynonyms>(90, &pass_instances);
-  MaybeAddRepeatedPass<FuzzerPassReplaceIrrelevantIds>(90, &pass_instances);
-
-  do {
-    // Each call to MaybeAddRepeatedPass randomly decides whether the given pass
-    // should be enabled, and adds an instance of the pass to |pass_instances|
-    // if it is enabled.
-    MaybeAddRepeatedPass<FuzzerPassAddAccessChains>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddBitInstructionSynonyms>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddCompositeExtract>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddCompositeInserts>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddCompositeTypes>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddCopyMemory>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddDeadBlocks>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddDeadBreaks>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddDeadContinues>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddEquationInstructions>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddFunctionCalls>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddGlobalVariables>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddImageSampleUnusedComponents>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddLoads>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddLocalVariables>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddLoopPreheaders>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddLoopsToCreateIntConstantSynonyms>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddOpPhiSynonyms>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddParameters>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddRelaxedDecorations>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddStores>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddSynonyms>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassAddVectorShuffleInstructions>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassConstructComposites>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassCopyObjects>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassDonateModules>(&pass_instances,
-                                                  donor_suppliers_);
-    MaybeAddRepeatedPass<FuzzerPassDuplicateRegionsWithSelections>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassExpandVectorReductions>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassFlattenConditionalBranches>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassInlineFunctions>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassInvertComparisonOperators>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassMakeVectorOperationsDynamic>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassMergeBlocks>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassMergeFunctionReturns>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassMutatePointers>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassOutlineFunctions>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassPermuteBlocks>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassPermuteFunctionParameters>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassPermuteInstructions>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsDown>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassPropagateInstructionsUp>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassPushIdsThroughVariables>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceAddsSubsMulsWithCarryingExtended>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceBranchesFromDeadBlocksWithExits>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceCopyMemoriesWithLoadsStores>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceCopyObjectsWithStoresLoads>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceLoadsStoresWithCopyMemories>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceParameterWithGlobal>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceLinearAlgebraInstructions>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceOpPhiIdsFromDeadPredecessors>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceOpSelectsWithConditionalBranches>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassReplaceParamsWithStruct>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassSplitBlocks>(&pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassSwapBranchConditionalOperands>(
-        &pass_instances);
-    MaybeAddRepeatedPass<FuzzerPassWrapRegionsInSelections>(&pass_instances);
-    // There is a theoretical possibility that no pass instances were created
-    // until now; loop again if so.
-  } while (pass_instances.GetPasses().empty());
-
-  RepeatedPassRecommenderStandard pass_recommender(&pass_instances,
-                                                   fuzzer_context_.get());
-
-  std::unique_ptr<RepeatedPassManager> repeated_pass_manager = nullptr;
-  switch (repeated_pass_strategy_) {
-    case RepeatedPassStrategy::kSimple:
-      repeated_pass_manager = MakeUnique<RepeatedPassManagerSimple>(
-          fuzzer_context_.get(), &pass_instances);
-      break;
-    case RepeatedPassStrategy::kLoopedWithRecommendations:
-      repeated_pass_manager =
-          MakeUnique<RepeatedPassManagerLoopedWithRecommendations>(
-              fuzzer_context_.get(), &pass_instances, &pass_recommender);
-      break;
-    case RepeatedPassStrategy::kRandomWithRecommendations:
-      repeated_pass_manager =
-          MakeUnique<RepeatedPassManagerRandomWithRecommendations>(
-              fuzzer_context_.get(), &pass_instances, &pass_recommender);
-      break;
-  }
-
-  do {
-    if (!ApplyPassAndCheckValidity(
-            repeated_pass_manager->ChoosePass(transformation_sequence_out_))) {
-      return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule,
-              std::vector<uint32_t>(), protobufs::TransformationSequence()};
-    }
-  } while (ShouldContinueFuzzing());
-
-  // Now apply some passes that it does not make sense to apply repeatedly,
-  // as they do not unlock other passes.
-  std::vector<std::unique_ptr<FuzzerPass>> final_passes;
-  MaybeAddFinalPass<FuzzerPassAdjustBranchWeights>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassAdjustFunctionControls>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassAdjustLoopControls>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassAdjustMemoryOperandsMasks>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassAdjustSelectionControls>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassAddNoContractionDecorations>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassInterchangeSignednessOfIntegerOperands>(
-      &final_passes);
-  MaybeAddFinalPass<FuzzerPassInterchangeZeroLikeConstants>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassPermutePhiOperands>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassSwapCommutableOperands>(&final_passes);
-  MaybeAddFinalPass<FuzzerPassToggleAccessChainInstruction>(&final_passes);
-  for (auto& pass : final_passes) {
-    if (!ApplyPassAndCheckValidity(pass.get())) {
-      return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule,
-              std::vector<uint32_t>(), protobufs::TransformationSequence()};
-    }
-  }
-  // Encode the module as a binary.
-  std::vector<uint32_t> binary_out;
-  ir_context_->module()->ToBinary(&binary_out, false);
-
-  return {Fuzzer::FuzzerResultStatus::kComplete, std::move(binary_out),
-          std::move(transformation_sequence_out_)};
+const protobufs::TransformationSequence& Fuzzer::GetTransformationSequence()
+    const {
+  return transformation_sequence_out_;
 }
 
-bool Fuzzer::ShouldContinueFuzzing() {
-  // There's a risk that fuzzing could get stuck, if none of the enabled fuzzer
-  // passes are able to apply any transformations.  To guard against this we
-  // count the number of times some repeated pass has been applied and ensure
-  // that fuzzing stops if the number of repeated passes hits the limit on the
-  // number of transformations that can be applied.
-  assert(
-      num_repeated_passes_applied_ <=
-          fuzzer_context_->GetTransformationLimit() &&
-      "The number of repeated passes applied must not exceed its upper limit.");
-  if (ir_context_->module()->id_bound() >= fuzzer_context_->GetIdBoundLimit()) {
-    return false;
-  }
-  if (num_repeated_passes_applied_ ==
-      fuzzer_context_->GetTransformationLimit()) {
-    // Stop because fuzzing has got stuck.
-    return false;
-  }
-  auto transformations_applied_so_far =
+Fuzzer::Result Fuzzer::Run(uint32_t num_of_transformations_to_apply) {
+  assert(is_valid_ && "The module was invalidated during the previous fuzzing");
+
+  const auto initial_num_of_transformations =
       static_cast<uint32_t>(transformation_sequence_out_.transformation_size());
-  if (transformations_applied_so_far >=
-      fuzzer_context_->GetTransformationLimit()) {
-    // Stop because we have reached the transformation limit.
-    return false;
+
+  auto status = Status::kComplete;
+  do {
+    if (!ApplyPassAndCheckValidity(
+            repeated_pass_manager_->ChoosePass(transformation_sequence_out_))) {
+      status = Status::kFuzzerPassLedToInvalidModule;
+      break;
+    }
+
+    // Check that the module is small enough.
+    if (ir_context_->module()->id_bound() >=
+        fuzzer_context_->GetIdBoundLimit()) {
+      status = Status::kModuleTooBig;
+      break;
+    }
+
+    auto transformations_applied_so_far = static_cast<uint32_t>(
+        transformation_sequence_out_.transformation_size());
+    assert(transformations_applied_so_far >= initial_num_of_transformations &&
+           "Number of transformations cannot decrease");
+
+    // Check if we've already applied the maximum number of transformations.
+    if (transformations_applied_so_far >=
+        fuzzer_context_->GetTransformationLimit()) {
+      status = Status::kTransformationLimitReached;
+      break;
+    }
+
+    // Check that we've not got stuck (this can happen if the only available
+    // fuzzer passes are not able to apply any transformations, or can only
+    // apply very few transformations).
+    if (num_repeated_passes_applied_ >=
+        fuzzer_context_->GetTransformationLimit()) {
+      status = Status::kFuzzerStuck;
+      break;
+    }
+
+    // Check whether we've exceeded the number of transformations we can apply
+    // in a single call to this method.
+    if (num_of_transformations_to_apply != 0 &&
+        transformations_applied_so_far - initial_num_of_transformations >=
+            num_of_transformations_to_apply) {
+      status = Status::kComplete;
+      break;
+    }
+
+  } while (ShouldContinueRepeatedPasses(num_of_transformations_to_apply == 0));
+
+  if (status != Status::kFuzzerPassLedToInvalidModule) {
+    // We apply this transformations despite the fact that we might exceed
+    // |num_of_transformations_to_apply|. This is not a problem for us since
+    // these fuzzer passes are relatively simple yet might trigger some bugs.
+    for (auto& pass : final_passes_) {
+      if (!ApplyPassAndCheckValidity(pass.get())) {
+        status = Status::kFuzzerPassLedToInvalidModule;
+        break;
+      }
+    }
   }
-  // If we have applied T transformations so far, and the limit on the number of
-  // transformations to apply is L (where T < L), the chance that we will
-  // continue fuzzing is:
-  //
-  //     1 - T/(2*L)
-  //
-  // That is, the chance of continuing decreases as more transformations are
-  // applied.  Using 2*L instead of L increases the number of transformations
-  // that are applied on average.
-  auto chance_of_continuing = static_cast<uint32_t>(
-      100.0 * (1.0 - (static_cast<double>(transformations_applied_so_far) /
-                      (2.0 * static_cast<double>(
-                                 fuzzer_context_->GetTransformationLimit())))));
-  if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) {
-    // We have probabilistically decided to stop.
-    return false;
+
+  is_valid_ = status != Status::kFuzzerPassLedToInvalidModule;
+  return {status, static_cast<uint32_t>(
+                      transformation_sequence_out_.transformation_size()) !=
+                      initial_num_of_transformations};
+}
+
+bool Fuzzer::ShouldContinueRepeatedPasses(
+    bool continue_fuzzing_probabilistically) {
+  if (continue_fuzzing_probabilistically) {
+    // If we have applied T transformations so far, and the limit on the number
+    // of transformations to apply is L (where T < L), the chance that we will
+    // continue fuzzing is:
+    //
+    //     1 - T/(2*L)
+    //
+    // That is, the chance of continuing decreases as more transformations are
+    // applied.  Using 2*L instead of L increases the number of transformations
+    // that are applied on average.
+    auto transformations_applied_so_far = static_cast<uint32_t>(
+        transformation_sequence_out_.transformation_size());
+    auto chance_of_continuing = static_cast<uint32_t>(
+        100.0 *
+        (1.0 - (static_cast<double>(transformations_applied_so_far) /
+                (2.0 * static_cast<double>(
+                           fuzzer_context_->GetTransformationLimit())))));
+    if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) {
+      // We have probabilistically decided to stop.
+      return false;
+    }
   }
   // Continue fuzzing!
   num_repeated_passes_applied_++;
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
index 774457f..4c38977 100644
--- a/source/fuzz/fuzzer.h
+++ b/source/fuzz/fuzzer.h
@@ -23,6 +23,7 @@
 #include "source/fuzz/fuzzer_pass.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/pass_management/repeated_pass_instances.h"
+#include "source/fuzz/pass_management/repeated_pass_manager.h"
 #include "source/fuzz/pass_management/repeated_pass_recommender.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/random_generator.h"
@@ -37,36 +38,32 @@
 class Fuzzer {
  public:
   // Possible statuses that can result from running the fuzzer.
-  enum class FuzzerResultStatus {
+  enum class Status {
     kComplete,
-    kFailedToCreateSpirvToolsInterface,
+    kModuleTooBig,
+    kTransformationLimitReached,
+    kFuzzerStuck,
     kFuzzerPassLedToInvalidModule,
-    kInitialBinaryInvalid,
   };
 
-  struct FuzzerResult {
-    FuzzerResultStatus status;
-    std::vector<uint32_t> transformed_binary;
-    protobufs::TransformationSequence applied_transformations;
+  struct Result {
+    // Status of the fuzzing session.
+    Status status;
+
+    // Equals to true if new transformations were applied during the previous
+    // fuzzing session.
+    bool is_changed;
   };
 
-  // Each field of this enum corresponds to an available repeated pass
-  // strategy, and is used to decide which kind of RepeatedPassManager object
-  // to create.
-  enum class RepeatedPassStrategy {
-    kSimple,
-    kRandomWithRecommendations,
-    kLoopedWithRecommendations
-  };
-
-  Fuzzer(spv_target_env target_env, MessageConsumer consumer,
-         const std::vector<uint32_t>& binary_in,
-         const protobufs::FactSequence& initial_facts,
+  Fuzzer(std::unique_ptr<opt::IRContext> ir_context,
+         std::unique_ptr<TransformationContext> transformation_context,
+         std::unique_ptr<FuzzerContext> fuzzer_context,
+         MessageConsumer consumer,
          const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
-         std::unique_ptr<RandomGenerator> random_generator,
          bool enable_all_passes, RepeatedPassStrategy repeated_pass_strategy,
          bool validate_after_each_fuzzer_pass,
-         spv_validator_options validator_options);
+         spv_validator_options validator_options,
+         bool ignore_inapplicable_transformations = true);
 
   // Disables copy/move constructor/assignment operations.
   Fuzzer(const Fuzzer&) = delete;
@@ -76,15 +73,23 @@
 
   ~Fuzzer();
 
-  // Transforms |binary_in_| by running a number of randomized fuzzer passes.
-  // Initial facts about the input binary and the context in which it will
-  // execute are provided via |initial_facts_|.  A source of donor modules to be
-  // used by transformations is provided via |donor_suppliers_|.  On success,
-  // returns a successful result status together with the transformed binary and
-  // the sequence of transformations that were applied.  Otherwise, returns an
-  // appropriate result status together with an empty binary and empty
-  // transformation sequence.
-  FuzzerResult Run();
+  // Transforms |ir_context_| by running a number of randomized fuzzer passes.
+  // Initial facts about the input binary and the context in which it will be
+  // executed are provided with |transformation_context_|.
+  // |num_of_transformations| is equal to the maximum number of transformations
+  // applied in a single call to this method. This parameter is ignored if its
+  // value is equal to 0. Because fuzzing cannot stop mid way through a fuzzer
+  // pass, fuzzing will stop after the fuzzer pass that exceeds
+  // |num_of_transformations| has completed, so that the total number of
+  // transformations may be somewhat larger than this number.
+  Result Run(uint32_t num_of_transformations_to_apply);
+
+  // Returns the current IR context. It may be invalid if the Run method
+  // returned Status::kFuzzerPassLedToInvalidModule previously.
+  opt::IRContext* GetIRContext();
+
+  // Returns the sequence of applied transformations.
+  const protobufs::TransformationSequence& GetTransformationSequence() const;
 
  private:
   // A convenience method to add a repeated fuzzer pass to |pass_instances| with
@@ -119,7 +124,9 @@
 
   // Decides whether to apply more repeated passes. The probability decreases as
   // the number of transformations that have been applied increases.
-  bool ShouldContinueFuzzing();
+  // The described probability is only applied if
+  // |continue_fuzzing_probabilistically| is true.
+  bool ShouldContinueRepeatedPasses(bool continue_fuzzing_probabilistically);
 
   // Applies |pass|, which must be a pass constructed with |ir_context|.
   // If |validate_after_each_fuzzer_pass_| is not set, true is always returned.
@@ -128,57 +135,65 @@
   // instruction has a distinct unique id.
   bool ApplyPassAndCheckValidity(FuzzerPass* pass) const;
 
-  // Target environment.
-  const spv_target_env target_env_;
-
   // Message consumer that will be invoked once for each message communicated
   // from the library.
-  MessageConsumer consumer_;
-
-  // The initial binary to which fuzzing should be applied.
-  const std::vector<uint32_t>& binary_in_;
-
-  // Initial facts known to hold in advance of applying any transformations.
-  const protobufs::FactSequence& initial_facts_;
-
-  // A source of modules whose contents can be donated into the module being
-  // fuzzed.
-  const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers_;
-
-  // Random number generator to control decision making during fuzzing.
-  std::unique_ptr<RandomGenerator> random_generator_;
+  const MessageConsumer consumer_;
 
   // Determines whether all passes should be enabled, vs. having passes be
   // probabilistically enabled.
-  bool enable_all_passes_;
-
-  // Controls which type of RepeatedPassManager object to create.
-  RepeatedPassStrategy repeated_pass_strategy_;
+  const bool enable_all_passes_;
 
   // Determines whether the validator should be invoked after every fuzzer pass.
-  bool validate_after_each_fuzzer_pass_;
+  const bool validate_after_each_fuzzer_pass_;
 
   // Options to control validation.
-  spv_validator_options validator_options_;
+  const spv_validator_options validator_options_;
 
   // The number of repeated fuzzer passes that have been applied is kept track
   // of, in order to enforce a hard limit on the number of times such passes
   // can be applied.
   uint32_t num_repeated_passes_applied_;
 
+  // We use this to determine whether we can continue fuzzing incrementally
+  // since the previous call to the Run method could've returned
+  // kFuzzerPassLedToInvalidModule.
+  bool is_valid_;
+
   // Intermediate representation for the module being fuzzed, which gets
   // mutated as fuzzing proceeds.
   std::unique_ptr<opt::IRContext> ir_context_;
 
+  // Contextual information that is required in order to apply
+  // transformations.
+  std::unique_ptr<TransformationContext> transformation_context_;
+
   // Provides probabilities that control the fuzzing process.
   std::unique_ptr<FuzzerContext> fuzzer_context_;
 
-  // Contextual information that is required in order to apply transformations.
-  std::unique_ptr<TransformationContext> transformation_context_;
-
-  // The sequence of transformations that have been applied during fuzzing.  It
+  // The sequence of transformations that have been applied during fuzzing. It
   // is initially empty and grows as fuzzer passes are applied.
   protobufs::TransformationSequence transformation_sequence_out_;
+
+  // This object contains instances of all fuzzer passes that will participate
+  // in the fuzzing.
+  RepeatedPassInstances pass_instances_;
+
+  // This object defines the recommendation logic for fuzzer passes.
+  std::unique_ptr<RepeatedPassRecommender> repeated_pass_recommender_;
+
+  // This object manager a list of fuzzer pass and their available
+  // recommendations.
+  std::unique_ptr<RepeatedPassManager> repeated_pass_manager_;
+
+  // Some passes that it does not make sense to apply repeatedly, as they do not
+  // unlock other passes.
+  std::vector<std::unique_ptr<FuzzerPass>> final_passes_;
+
+  // When set, this flag causes inapplicable transformations that should be
+  // applicable by construction to be ignored. This is useful when the fuzzer
+  // is being deployed at scale to test a SPIR-V processing tool, and where it
+  // is desirable to ignore bugs in the fuzzer itself.
+  const bool ignore_inapplicable_transformations_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 47bf4e2..c217542 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -21,6 +21,12 @@
 
 namespace {
 
+// An offset between the the module's id bound and the minimum fresh id.
+//
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541): consider
+//  the case where the maximum id bound is reached.
+const uint32_t kIdBoundGap = 100;
+
 // Limits to help control the overall fuzzing process and rein in individual
 // fuzzer passes.
 const uint32_t kIdBoundLimit = 50000;
@@ -36,6 +42,8 @@
 const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
                                                                          90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
+const std::pair<uint32_t, uint32_t> KChanceOfAddingAtomicLoad = {30, 90};
+const std::pair<uint32_t, uint32_t> KChanceOfAddingAtomicStore = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingBitInstructionSynonym = {5,
                                                                             20};
 const std::pair<uint32_t, uint32_t>
@@ -114,6 +122,8 @@
 const std::pair<uint32_t, uint32_t> kChanceOfMutatingPointer = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfPermutingFunctionVariables = {30,
+                                                                           90};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingInstructions = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
@@ -145,12 +155,16 @@
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = {
     20, 40};
 const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfSwappingAnotherPairOfFunctionVariables = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfSwappingConditionalBranchOperands =
     {10, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfSwappingFunctions = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfTogglingAccessChainInstruction = {
     20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfWrappingRegionInSelection = {70,
                                                                           90};
+const std::pair<uint32_t, uint32_t> kChanceOfWrappingVectorSynonym = {10, 90};
 
 // Default limits for various quantities that are chosen during fuzzing.
 // Keep them in alphabetical order.
@@ -177,10 +191,11 @@
 
 }  // namespace
 
-FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
-                             uint32_t min_fresh_id)
-    : random_generator_(random_generator),
+FuzzerContext::FuzzerContext(std::unique_ptr<RandomGenerator> random_generator,
+                             uint32_t min_fresh_id, bool is_wgsl_compatible)
+    : random_generator_(std::move(random_generator)),
       next_fresh_id_(min_fresh_id),
+      is_wgsl_compatible_(is_wgsl_compatible),
       max_equivalence_class_size_for_data_synonym_fact_closure_(
           kDefaultMaxEquivalenceClassSizeForDataSynonymFactClosure),
       max_loop_control_partial_count_(kDefaultMaxLoopControlPartialCount),
@@ -203,6 +218,10 @@
       ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
   chance_of_adding_array_or_struct_type_ =
       ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
+  chance_of_adding_atomic_load_ =
+      ChooseBetweenMinAndMax(KChanceOfAddingAtomicLoad);
+  chance_of_adding_atomic_store_ =
+      ChooseBetweenMinAndMax(KChanceOfAddingAtomicStore);
   chance_of_adding_bit_instruction_synonym_ =
       ChooseBetweenMinAndMax(kChanceOfAddingBitInstructionSynonym);
   chance_of_adding_both_branches_when_replacing_opselect_ =
@@ -308,6 +327,8 @@
       ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
   chance_of_outlining_function_ =
       ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
+  chance_of_permuting_function_variables_ =
+      ChooseBetweenMinAndMax(kChanceOfPermutingFunctionVariables);
   chance_of_permuting_instructions_ =
       ChooseBetweenMinAndMax(kChanceOfPermutingInstructions);
   chance_of_permuting_parameters_ =
@@ -345,12 +366,18 @@
   chance_of_replacing_parameters_with_struct_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithStruct);
   chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
+  chance_of_swapping_another_pair_of_function_variables_ =
+      ChooseBetweenMinAndMax(kChanceOfSwappingAnotherPairOfFunctionVariables);
   chance_of_swapping_conditional_branch_operands_ =
       ChooseBetweenMinAndMax(kChanceOfSwappingConditionalBranchOperands);
+  chance_of_swapping_functions_ =
+      ChooseBetweenMinAndMax(kChanceOfSwappingFunctions);
   chance_of_toggling_access_chain_instruction_ =
       ChooseBetweenMinAndMax(kChanceOfTogglingAccessChainInstruction);
   chance_of_wrapping_region_in_selection_ =
       ChooseBetweenMinAndMax(kChanceOfWrappingRegionInSelection);
+  chance_of_wrapping_vector_synonym_ =
+      ChooseBetweenMinAndMax(kChanceOfWrappingVectorSynonym);
 }
 
 FuzzerContext::~FuzzerContext() = default;
@@ -403,5 +430,9 @@
   return kTransformationLimit;
 }
 
+uint32_t FuzzerContext::GetMinFreshId(opt::IRContext* ir_context) {
+  return ir_context->module()->id_bound() + kIdBoundGap;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 8c51041..77a5d40 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -21,6 +21,7 @@
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/random_generator.h"
 #include "source/opt/function.h"
+#include "source/opt/ir_context.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -32,7 +33,8 @@
  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(std::unique_ptr<RandomGenerator> random_generator,
+                uint32_t min_fresh_id, bool is_wgsl_compatible);
 
   ~FuzzerContext();
 
@@ -64,7 +66,7 @@
   }
 
   // Randomly shuffles a |sequence| between |lo| and |hi| indices inclusively.
-  // |lo| and |hi| must be valid indices to the |sequence|
+  // |lo| and |hi| must be valid indices to the |sequence|.
   template <typename T>
   void Shuffle(std::vector<T>* sequence, size_t lo, size_t hi) const {
     auto& array = *sequence;
@@ -89,7 +91,7 @@
     }
   }
 
-  // Ramdomly shuffles a |sequence|
+  // Randomly shuffles a |sequence|.
   template <typename T>
   void Shuffle(std::vector<T>* sequence) const {
     if (!sequence->empty()) {
@@ -102,7 +104,7 @@
   uint32_t GetFreshId();
 
   // Returns a vector of |count| fresh ids.
-  std::vector<uint32_t> GetFreshIds(const uint32_t count);
+  std::vector<uint32_t> GetFreshIds(uint32_t count);
 
   // A suggested limit on the id bound for the module being fuzzed.  This is
   // useful for deciding when to stop the overall fuzzing process.  Furthermore,
@@ -115,6 +117,14 @@
   // fuzzer passes.
   uint32_t GetTransformationLimit() const;
 
+  // Returns the minimum fresh id that can be used given the |ir_context|.
+  static uint32_t GetMinFreshId(opt::IRContext* ir_context);
+
+  // Returns true if all transformations should be compatible with WGSL.
+  bool IsWgslCompatible() const {
+    return is_wgsl_compatible_;
+  }
+
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
   uint32_t GetChanceOfAcceptingRepeatedPassRecommendation() const {
@@ -132,6 +142,12 @@
   uint32_t GetChanceOfAddingArrayOrStructType() const {
     return chance_of_adding_array_or_struct_type_;
   }
+  uint32_t GetChanceOfAddingAtomicLoad() const {
+    return chance_of_adding_atomic_load_;
+  }
+  uint32_t GetChanceOfAddingAtomicStore() const {
+    return chance_of_adding_atomic_store_;
+  }
   uint32_t GetChanceOfAddingBitInstructionSynonym() const {
     return chance_of_adding_bit_instruction_synonym_;
   }
@@ -293,6 +309,9 @@
   uint32_t GetChanceOfOutliningFunction() const {
     return chance_of_outlining_function_;
   }
+  uint32_t GetChanceOfPermutingFunctionVariables() const {
+    return chance_of_permuting_function_variables_;
+  }
   uint32_t GetChanceOfPermutingInstructions() const {
     return chance_of_permuting_instructions_;
   }
@@ -350,9 +369,17 @@
   uint32_t GetChanceOfSplittingBlock() const {
     return chance_of_splitting_block_;
   }
+  uint32_t GetChanceOfSwappingAnotherPairOfFunctionVariables() const {
+    return chance_of_swapping_another_pair_of_function_variables_;
+  }
   uint32_t GetChanceOfSwappingConditionalBranchOperands() const {
     return chance_of_swapping_conditional_branch_operands_;
   }
+
+  uint32_t GetChanceOfSwappingFunctions() const {
+    return chance_of_swapping_functions_;
+  }
+
   uint32_t GetChanceOfTogglingAccessChainInstruction() const {
     return chance_of_toggling_access_chain_instruction_;
   }
@@ -360,6 +387,10 @@
     return chance_of_wrapping_region_in_selection_;
   }
 
+  uint32_t GetChanceOfWrappingVectorSynonym() const {
+    return chance_of_wrapping_vector_synonym_;
+  }
+
   // Other functions to control transformations. Keep them in alphabetical
   // order.
   uint32_t GetMaximumEquivalenceClassSizeForDataSynonymFactClosure() const {
@@ -404,6 +435,9 @@
   uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) {
     return random_generator_->RandomUint32(number_of_components);
   }
+  uint32_t GetRandomIndexForWrappingVector(uint32_t vector_width) {
+    return random_generator_->RandomUint32(vector_width);
+  }
   int64_t GetRandomValueForStepConstantInLoop() {
     return random_generator_->RandomUint64(UINT64_MAX);
   }
@@ -441,16 +475,22 @@
     // Ensure that the number of unused components is non-zero.
     return random_generator_->RandomUint32(max_unused_component_count) + 1;
   }
+  uint32_t GetWidthOfWrappingVector() {
+    return 2 + random_generator_->RandomUint32(3);
+  }
   bool GoDeeperInConstantObfuscation(uint32_t depth) {
-    return go_deeper_in_constant_obfuscation_(depth, random_generator_);
+    return go_deeper_in_constant_obfuscation_(depth, random_generator_.get());
   }
 
  private:
   // The source of randomness.
-  RandomGenerator* random_generator_;
+  std::unique_ptr<RandomGenerator> random_generator_;
   // The next fresh id to be issued.
   uint32_t next_fresh_id_;
 
+  // True if all transformations should be compatible with WGSL spec.
+  bool is_wgsl_compatible_;
+
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
   uint32_t chance_of_accepting_repeated_pass_recommendation_;
@@ -458,6 +498,8 @@
   uint32_t chance_of_adding_another_pass_to_pass_loop_;
   uint32_t chance_of_adding_another_struct_field_;
   uint32_t chance_of_adding_array_or_struct_type_;
+  uint32_t chance_of_adding_atomic_load_;
+  uint32_t chance_of_adding_atomic_store_;
   uint32_t chance_of_adding_bit_instruction_synonym_;
   uint32_t chance_of_adding_both_branches_when_replacing_opselect_;
   uint32_t chance_of_adding_composite_extract_;
@@ -513,6 +555,7 @@
   uint32_t chance_of_mutating_pointer_;
   uint32_t chance_of_obfuscating_constant_;
   uint32_t chance_of_outlining_function_;
+  uint32_t chance_of_permuting_function_variables_;
   uint32_t chance_of_permuting_instructions_;
   uint32_t chance_of_permuting_parameters_;
   uint32_t chance_of_permuting_phi_operands_;
@@ -532,9 +575,12 @@
   uint32_t chance_of_replacing_parameters_with_globals_;
   uint32_t chance_of_replacing_parameters_with_struct_;
   uint32_t chance_of_splitting_block_;
+  uint32_t chance_of_swapping_another_pair_of_function_variables_;
   uint32_t chance_of_swapping_conditional_branch_operands_;
+  uint32_t chance_of_swapping_functions_;
   uint32_t chance_of_toggling_access_chain_instruction_;
   uint32_t chance_of_wrapping_region_in_selection_;
+  uint32_t chance_of_wrapping_vector_synonym_;
 
   // Limits associated with various quantities for which random values are
   // chosen during fuzzing.
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 486f18f..d91482c 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -43,11 +43,14 @@
 FuzzerPass::FuzzerPass(opt::IRContext* ir_context,
                        TransformationContext* transformation_context,
                        FuzzerContext* fuzzer_context,
-                       protobufs::TransformationSequence* transformations)
+                       protobufs::TransformationSequence* transformations,
+                       bool ignore_inapplicable_transformations)
     : ir_context_(ir_context),
       transformation_context_(transformation_context),
       fuzzer_context_(fuzzer_context),
-      transformations_(transformations) {}
+      transformations_(transformations),
+      ignore_inapplicable_transformations_(
+          ignore_inapplicable_transformations) {}
 
 FuzzerPass::~FuzzerPass() = default;
 
@@ -111,10 +114,8 @@
   // module.
   std::vector<opt::BasicBlock*> reachable_blocks;
 
-  const auto* dominator_analysis =
-      GetIRContext()->GetDominatorAnalysis(function);
   for (auto& block : *function) {
-    if (dominator_analysis->IsReachable(&block)) {
+    if (GetIRContext()->IsReachable(block)) {
       reachable_blocks.push_back(&block);
     }
   }
@@ -184,6 +185,49 @@
   }
 }
 
+void FuzzerPass::ApplyTransformation(const Transformation& transformation) {
+  if (ignore_inapplicable_transformations_) {
+    // If an applicable-by-construction transformation turns out to be
+    // inapplicable, this is a bug in the fuzzer. However, when deploying the
+    // fuzzer at scale for finding bugs in SPIR-V processing tools it is
+    // desirable to silently ignore such bugs. This code path caters for that
+    // scenario.
+    if (!transformation.IsApplicable(GetIRContext(),
+                                     *GetTransformationContext())) {
+      return;
+    }
+  } else {
+    // This code path caters for debugging bugs in the fuzzer, where an
+    // applicable-by-construction transformation turns out to be inapplicable.
+    assert(transformation.IsApplicable(GetIRContext(),
+                                       *GetTransformationContext()) &&
+           "Transformation should be applicable by construction.");
+  }
+  transformation.Apply(GetIRContext(), GetTransformationContext());
+  auto transformation_message = transformation.ToMessage();
+  assert(transformation_message.transformation_case() !=
+             protobufs::Transformation::TRANSFORMATION_NOT_SET &&
+         "Bad transformation.");
+  *GetTransformations()->add_transformation() =
+      std::move(transformation_message);
+}
+
+bool FuzzerPass::MaybeApplyTransformation(
+    const Transformation& transformation) {
+  if (transformation.IsApplicable(GetIRContext(),
+                                  *GetTransformationContext())) {
+    transformation.Apply(GetIRContext(), GetTransformationContext());
+    auto transformation_message = transformation.ToMessage();
+    assert(transformation_message.transformation_case() !=
+               protobufs::Transformation::TRANSFORMATION_NOT_SET &&
+           "Bad transformation.");
+    *GetTransformations()->add_transformation() =
+        std::move(transformation_message);
+    return true;
+  }
+  return false;
+}
+
 uint32_t FuzzerPass::FindOrCreateBoolType() {
   if (auto existing_id = fuzzerutil::MaybeGetBoolType(GetIRContext())) {
     return existing_id;
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index da3f239..2655b54 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -33,7 +33,8 @@
   FuzzerPass(opt::IRContext* ir_context,
              TransformationContext* transformation_context,
              FuzzerContext* fuzzer_context,
-             protobufs::TransformationSequence* transformations);
+             protobufs::TransformationSequence* transformations,
+             bool ignore_inapplicable_transformations);
 
   virtual ~FuzzerPass();
 
@@ -106,37 +107,13 @@
 
   // A generic helper for applying a transformation that should be applicable
   // by construction, and adding it to the sequence of applied transformations.
-  void ApplyTransformation(const Transformation& transformation) {
-    assert(transformation.IsApplicable(GetIRContext(),
-                                       *GetTransformationContext()) &&
-           "Transformation should be applicable by construction.");
-    transformation.Apply(GetIRContext(), GetTransformationContext());
-    protobufs::Transformation transformation_message =
-        transformation.ToMessage();
-    assert(transformation_message.transformation_case() !=
-               protobufs::Transformation::TRANSFORMATION_NOT_SET &&
-           "Bad transformation.");
-    *GetTransformations()->add_transformation() = transformation_message;
-  }
+  void ApplyTransformation(const Transformation& transformation);
 
   // A generic helper for applying a transformation only if it is applicable.
   // If it is applicable, the transformation is applied and then added to the
   // sequence of applied transformations and the function returns true.
   // Otherwise, the function returns false.
-  bool MaybeApplyTransformation(const Transformation& transformation) {
-    if (transformation.IsApplicable(GetIRContext(),
-                                    *GetTransformationContext())) {
-      transformation.Apply(GetIRContext(), GetTransformationContext());
-      protobufs::Transformation transformation_message =
-          transformation.ToMessage();
-      assert(transformation_message.transformation_case() !=
-                 protobufs::Transformation::TRANSFORMATION_NOT_SET &&
-             "Bad transformation.");
-      *GetTransformations()->add_transformation() = transformation_message;
-      return true;
-    }
-    return false;
-  }
+  bool MaybeApplyTransformation(const Transformation& transformation);
 
   // Returns the id of an OpTypeBool instruction.  If such an instruction does
   // not exist, a transformation is applied to add it.
@@ -345,6 +322,10 @@
   TransformationContext* transformation_context_;
   FuzzerContext* fuzzer_context_;
   protobufs::TransformationSequence* transformations_;
+  // If set, then transformations that should be applicable by construction are
+  // still tested for applicability, and ignored if they turn out to be
+  // inapplicable. Otherwise, applicability by construction is asserted.
+  const bool ignore_inapplicable_transformations_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_add_access_chains.cpp b/source/fuzz/fuzzer_pass_add_access_chains.cpp
index 11155f2..39f193d 100644
--- a/source/fuzz/fuzzer_pass_add_access_chains.cpp
+++ b/source/fuzz/fuzzer_pass_add_access_chains.cpp
@@ -23,11 +23,10 @@
 FuzzerPassAddAccessChains::FuzzerPassAddAccessChains(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddAccessChains::~FuzzerPassAddAccessChains() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddAccessChains::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_add_access_chains.h b/source/fuzz/fuzzer_pass_add_access_chains.h
index 8649296..5e209cd 100644
--- a/source/fuzz/fuzzer_pass_add_access_chains.h
+++ b/source/fuzz/fuzzer_pass_add_access_chains.h
@@ -28,9 +28,8 @@
   FuzzerPassAddAccessChains(opt::IRContext* ir_context,
                             TransformationContext* transformation_context,
                             FuzzerContext* fuzzer_context,
-                            protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddAccessChains();
+                            protobufs::TransformationSequence* transformations,
+                            bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp
index 7b9ac4e..1b0d7b1 100644
--- a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp
@@ -24,12 +24,10 @@
 FuzzerPassAddBitInstructionSynonyms::FuzzerPassAddBitInstructionSynonyms(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddBitInstructionSynonyms::~FuzzerPassAddBitInstructionSynonyms() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddBitInstructionSynonyms::Apply() {
   for (auto& function : *GetIRContext()->module()) {
@@ -48,22 +46,11 @@
           continue;
         }
 
-        // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557):
-        //  Right now we only support certain operations. When this issue is
-        //  addressed the following conditional can use the function
-        //  |spvOpcodeIsBit|.
-        if (instruction.opcode() != SpvOpBitwiseOr &&
-            instruction.opcode() != SpvOpBitwiseXor &&
-            instruction.opcode() != SpvOpBitwiseAnd &&
-            instruction.opcode() != SpvOpNot) {
-          continue;
-        }
-
-        // Right now, only integer operands are supported.
-        if (GetIRContext()
-                ->get_type_mgr()
-                ->GetType(instruction.type_id())
-                ->AsVector()) {
+        // Make sure fuzzer never applies a transformation to a bitwise
+        // instruction with differently signed operands, only integer operands
+        // are supported and bitwise operations are supported only.
+        if (!TransformationAddBitInstructionSynonym::IsInstructionSupported(
+                GetIRContext(), &instruction)) {
           continue;
         }
 
diff --git a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h
index 0194425..38d81aa 100644
--- a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h
+++ b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h
@@ -28,9 +28,8 @@
   FuzzerPassAddBitInstructionSynonyms(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddBitInstructionSynonyms();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_composite_extract.cpp b/source/fuzz/fuzzer_pass_add_composite_extract.cpp
index 132a49d..dbbec0c 100644
--- a/source/fuzz/fuzzer_pass_add_composite_extract.cpp
+++ b/source/fuzz/fuzzer_pass_add_composite_extract.cpp
@@ -14,6 +14,7 @@
 
 #include "source/fuzz/fuzzer_pass_add_composite_extract.h"
 
+#include "source/fuzz/available_instructions.h"
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
@@ -25,11 +26,10 @@
 FuzzerPassAddCompositeExtract::FuzzerPassAddCompositeExtract(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddCompositeExtract::~FuzzerPassAddCompositeExtract() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddCompositeExtract::Apply() {
   std::vector<const protobufs::DataDescriptor*> composite_synonyms;
@@ -41,14 +41,16 @@
     }
   }
 
-  // We don't want to invalidate the module every time we apply this
-  // transformation since rebuilding DominatorAnalysis can be expensive, so we
-  // collect up the transformations we wish to apply and apply them all later.
-  std::vector<TransformationCompositeExtract> transformations;
+  AvailableInstructions available_composites(
+      GetIRContext(), [](opt::IRContext* ir_context, opt::Instruction* inst) {
+        return inst->type_id() && inst->result_id() &&
+               fuzzerutil::IsCompositeType(
+                   ir_context->get_type_mgr()->GetType(inst->type_id()));
+      });
 
   ForEachInstructionWithInstructionDescriptor(
-      [this, &composite_synonyms, &transformations](
-          opt::Function* function, opt::BasicBlock* block,
+      [this, &available_composites, &composite_synonyms](
+          opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
           opt::BasicBlock::iterator inst_it,
           const protobufs::InstructionDescriptor& instruction_descriptor) {
         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
@@ -61,14 +63,6 @@
           return;
         }
 
-        auto available_composites = FindAvailableInstructions(
-            function, block, inst_it,
-            [](opt::IRContext* ir_context, opt::Instruction* inst) {
-              return inst->type_id() && inst->result_id() &&
-                     fuzzerutil::IsCompositeType(
-                         ir_context->get_type_mgr()->GetType(inst->type_id()));
-            });
-
         std::vector<const protobufs::DataDescriptor*> available_synonyms;
         for (const auto* dd : composite_synonyms) {
           if (fuzzerutil::IdIsAvailableBeforeInstruction(
@@ -77,18 +71,21 @@
           }
         }
 
-        if (available_synonyms.empty() && available_composites.empty()) {
+        auto candidate_composites =
+            available_composites.GetAvailableBeforeInstruction(&*inst_it);
+
+        if (available_synonyms.empty() && candidate_composites.empty()) {
           return;
         }
 
         uint32_t composite_id = 0;
         std::vector<uint32_t> indices;
 
-        if (available_synonyms.empty() || (!available_composites.empty() &&
+        if (available_synonyms.empty() || (!candidate_composites.empty() &&
                                            GetFuzzerContext()->ChooseEven())) {
           const auto* inst =
-              available_composites[GetFuzzerContext()->RandomIndex(
-                  available_composites)];
+              candidate_composites[GetFuzzerContext()->RandomIndex(
+                  candidate_composites)];
           composite_id = inst->result_id();
 
           auto type_id = inst->type_id();
@@ -153,14 +150,10 @@
         assert(composite_id != 0 && !indices.empty() &&
                "Composite object should have been chosen correctly");
 
-        transformations.emplace_back(instruction_descriptor,
-                                     GetFuzzerContext()->GetFreshId(),
-                                     composite_id, indices);
+        ApplyTransformation(TransformationCompositeExtract(
+            instruction_descriptor, GetFuzzerContext()->GetFreshId(),
+            composite_id, indices));
       });
-
-  for (const auto& transformation : transformations) {
-    ApplyTransformation(transformation);
-  }
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_add_composite_extract.h b/source/fuzz/fuzzer_pass_add_composite_extract.h
index 8bcb825..e7ed18a 100644
--- a/source/fuzz/fuzzer_pass_add_composite_extract.h
+++ b/source/fuzz/fuzzer_pass_add_composite_extract.h
@@ -27,9 +27,8 @@
   FuzzerPassAddCompositeExtract(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddCompositeExtract() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp
index e58c754..2ac12de 100644
--- a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp
+++ b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp
@@ -25,11 +25,10 @@
 FuzzerPassAddCompositeInserts::FuzzerPassAddCompositeInserts(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddCompositeInserts::~FuzzerPassAddCompositeInserts() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddCompositeInserts::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.h b/source/fuzz/fuzzer_pass_add_composite_inserts.h
index c4f5103..d9f42d5 100644
--- a/source/fuzz/fuzzer_pass_add_composite_inserts.h
+++ b/source/fuzz/fuzzer_pass_add_composite_inserts.h
@@ -27,9 +27,9 @@
   FuzzerPassAddCompositeInserts(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
-  ~FuzzerPassAddCompositeInserts();
   void Apply() override;
 
   // Checks if any component of a composite is a pointer.
diff --git a/source/fuzz/fuzzer_pass_add_composite_types.cpp b/source/fuzz/fuzzer_pass_add_composite_types.cpp
index c4d8d1c..af36ad0 100644
--- a/source/fuzz/fuzzer_pass_add_composite_types.cpp
+++ b/source/fuzz/fuzzer_pass_add_composite_types.cpp
@@ -24,11 +24,10 @@
 FuzzerPassAddCompositeTypes::FuzzerPassAddCompositeTypes(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddCompositeTypes::~FuzzerPassAddCompositeTypes() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddCompositeTypes::Apply() {
   MaybeAddMissingVectorTypes();
@@ -125,7 +124,9 @@
         break;
       case SpvOpTypeStruct: {
         if (!fuzzerutil::MembersHaveBuiltInDecoration(GetIRContext(),
-                                                      inst.result_id())) {
+                                                      inst.result_id()) &&
+            !fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
+                                                         inst.result_id())) {
           candidates.push_back(inst.result_id());
         }
       } break;
diff --git a/source/fuzz/fuzzer_pass_add_composite_types.h b/source/fuzz/fuzzer_pass_add_composite_types.h
index 87bc0ff..f16c79e 100644
--- a/source/fuzz/fuzzer_pass_add_composite_types.h
+++ b/source/fuzz/fuzzer_pass_add_composite_types.h
@@ -27,9 +27,8 @@
   FuzzerPassAddCompositeTypes(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddCompositeTypes();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_add_copy_memory.cpp b/source/fuzz/fuzzer_pass_add_copy_memory.cpp
index d98619c..6551f49 100644
--- a/source/fuzz/fuzzer_pass_add_copy_memory.cpp
+++ b/source/fuzz/fuzzer_pass_add_copy_memory.cpp
@@ -25,11 +25,10 @@
 FuzzerPassAddCopyMemory::FuzzerPassAddCopyMemory(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddCopyMemory::~FuzzerPassAddCopyMemory() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddCopyMemory::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_add_copy_memory.h b/source/fuzz/fuzzer_pass_add_copy_memory.h
index 321e4a1..e258752 100644
--- a/source/fuzz/fuzzer_pass_add_copy_memory.h
+++ b/source/fuzz/fuzzer_pass_add_copy_memory.h
@@ -27,9 +27,8 @@
   FuzzerPassAddCopyMemory(opt::IRContext* ir_context,
                           TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
-                          protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddCopyMemory() override;
+                          protobufs::TransformationSequence* transformations,
+                          bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
index 84ed1fb..82d53eb 100644
--- a/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_blocks.cpp
@@ -14,20 +14,27 @@
 
 #include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
 
+#include <algorithm>
+
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/transformation_add_dead_block.h"
 
 namespace spvtools {
 namespace fuzz {
 
+namespace {
+
+const size_t kMaxTransformationsInOnePass = 100U;
+
+}  // namespace
+
 FuzzerPassAddDeadBlocks::FuzzerPassAddDeadBlocks(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddDeadBlocks::~FuzzerPassAddDeadBlocks() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddDeadBlocks::Apply() {
   // We iterate over all blocks in the module collecting up those at which we
@@ -57,9 +64,18 @@
           GetFuzzerContext()->GetFreshId(), block.id(), condition_value));
     }
   }
-  // Apply all those transformations that are in fact applicable.
-  for (auto& transformation : candidate_transformations) {
-    MaybeApplyTransformation(transformation);
+  // Applying transformations can be expensive as each transformation requires
+  // dominator information and also invalidates dominator information. We thus
+  // limit the number of transformations that one application of this fuzzer
+  // pass can apply. We choose to do this after identifying all the
+  // transformations that we *might* want to apply, rather than breaking the
+  // above loops once the limit is reached, to avoid biasing towards
+  // transformations that target early parts of the module.
+  GetFuzzerContext()->Shuffle(&candidate_transformations);
+  for (size_t i = 0; i < std::min(kMaxTransformationsInOnePass,
+                                  candidate_transformations.size());
+       i++) {
+    MaybeApplyTransformation(candidate_transformations[i]);
   }
 }
 
diff --git a/source/fuzz/fuzzer_pass_add_dead_blocks.h b/source/fuzz/fuzzer_pass_add_dead_blocks.h
index d78f088..4567e87 100644
--- a/source/fuzz/fuzzer_pass_add_dead_blocks.h
+++ b/source/fuzz/fuzzer_pass_add_dead_blocks.h
@@ -27,9 +27,8 @@
   FuzzerPassAddDeadBlocks(opt::IRContext* ir_context,
                           TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
-                          protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddDeadBlocks();
+                          protobufs::TransformationSequence* transformations,
+                          bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
index e726c63..c3664c8 100644
--- a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
@@ -24,11 +24,10 @@
 FuzzerPassAddDeadBreaks::FuzzerPassAddDeadBreaks(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddDeadBreaks::~FuzzerPassAddDeadBreaks() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddDeadBreaks::Apply() {
   // We first collect up lots of possibly-applicable transformations.
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.h b/source/fuzz/fuzzer_pass_add_dead_breaks.h
index c379eed..361c346 100644
--- a/source/fuzz/fuzzer_pass_add_dead_breaks.h
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.h
@@ -26,9 +26,8 @@
   FuzzerPassAddDeadBreaks(opt::IRContext* ir_context,
                           TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
-                          protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddDeadBreaks();
+                          protobufs::TransformationSequence* transformations,
+                          bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
index 24617ae..3881481 100644
--- a/source/fuzz/fuzzer_pass_add_dead_continues.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
@@ -24,11 +24,10 @@
 FuzzerPassAddDeadContinues::FuzzerPassAddDeadContinues(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddDeadContinues::~FuzzerPassAddDeadContinues() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddDeadContinues::Apply() {
   // Consider every block in every function.
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.h b/source/fuzz/fuzzer_pass_add_dead_continues.h
index b2acb93..4f1bd60 100644
--- a/source/fuzz/fuzzer_pass_add_dead_continues.h
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.h
@@ -23,12 +23,11 @@
 // A fuzzer pass for adding dead continue edges to the module.
 class FuzzerPassAddDeadContinues : public FuzzerPass {
  public:
-  FuzzerPassAddDeadContinues(
-      opt::IRContext* ir_context, TransformationContext* transformation_context,
-      FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddDeadContinues();
+  FuzzerPassAddDeadContinues(opt::IRContext* ir_context,
+                             TransformationContext* transformation_context,
+                             FuzzerContext* fuzzer_context,
+                             protobufs::TransformationSequence* transformations,
+                             bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_equation_instructions.cpp b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
index 6376c9f..4bbded8 100644
--- a/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
+++ b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
@@ -45,12 +45,10 @@
 FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddEquationInstructions::~FuzzerPassAddEquationInstructions() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddEquationInstructions::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_add_equation_instructions.h b/source/fuzz/fuzzer_pass_add_equation_instructions.h
index 9ce581e..dc9a27b 100644
--- a/source/fuzz/fuzzer_pass_add_equation_instructions.h
+++ b/source/fuzz/fuzzer_pass_add_equation_instructions.h
@@ -29,9 +29,8 @@
   FuzzerPassAddEquationInstructions(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddEquationInstructions();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_add_function_calls.cpp b/source/fuzz/fuzzer_pass_add_function_calls.cpp
index 7400557..033f4a2 100644
--- a/source/fuzz/fuzzer_pass_add_function_calls.cpp
+++ b/source/fuzz/fuzzer_pass_add_function_calls.cpp
@@ -26,11 +26,10 @@
 FuzzerPassAddFunctionCalls::FuzzerPassAddFunctionCalls(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddFunctionCalls::~FuzzerPassAddFunctionCalls() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddFunctionCalls::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_add_function_calls.h b/source/fuzz/fuzzer_pass_add_function_calls.h
index 4ed8791..80b03d7 100644
--- a/source/fuzz/fuzzer_pass_add_function_calls.h
+++ b/source/fuzz/fuzzer_pass_add_function_calls.h
@@ -24,12 +24,11 @@
 // anywhere, and (b) any functions, from dead blocks.
 class FuzzerPassAddFunctionCalls : public FuzzerPass {
  public:
-  FuzzerPassAddFunctionCalls(
-      opt::IRContext* ir_context, TransformationContext* transformation_context,
-      FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddFunctionCalls();
+  FuzzerPassAddFunctionCalls(opt::IRContext* ir_context,
+                             TransformationContext* transformation_context,
+                             FuzzerContext* fuzzer_context,
+                             protobufs::TransformationSequence* transformations,
+                             bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.cpp b/source/fuzz/fuzzer_pass_add_global_variables.cpp
index 9a45a37..061f44d 100644
--- a/source/fuzz/fuzzer_pass_add_global_variables.cpp
+++ b/source/fuzz/fuzzer_pass_add_global_variables.cpp
@@ -23,11 +23,10 @@
 FuzzerPassAddGlobalVariables::FuzzerPassAddGlobalVariables(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddGlobalVariables::~FuzzerPassAddGlobalVariables() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddGlobalVariables::Apply() {
   SpvStorageClass variable_storage_class = SpvStorageClassPrivate;
@@ -49,6 +48,10 @@
 
   // These are the basic types that are available to this fuzzer pass.
   auto& basic_types = basic_type_ids_and_pointers.first;
+  if (basic_types.empty()) {
+    // There are no basic types, so there is nothing this fuzzer pass can do.
+    return;
+  }
 
   // These are the pointers to those basic types that are *initially* available
   // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
diff --git a/source/fuzz/fuzzer_pass_add_global_variables.h b/source/fuzz/fuzzer_pass_add_global_variables.h
index a907d36..1496646 100644
--- a/source/fuzz/fuzzer_pass_add_global_variables.h
+++ b/source/fuzz/fuzzer_pass_add_global_variables.h
@@ -27,9 +27,8 @@
   FuzzerPassAddGlobalVariables(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddGlobalVariables();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp
index 3095bb6..19661d1 100644
--- a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp
+++ b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp
@@ -27,12 +27,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddImageSampleUnusedComponents::
-    ~FuzzerPassAddImageSampleUnusedComponents() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddImageSampleUnusedComponents::Apply() {
   // SPIR-V module to help understand the transformation.
diff --git a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.h b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.h
index 26374c3..1a27893 100644
--- a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.h
+++ b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.h
@@ -28,9 +28,8 @@
   FuzzerPassAddImageSampleUnusedComponents(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddImageSampleUnusedComponents();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_loads.cpp b/source/fuzz/fuzzer_pass_add_loads.cpp
index 256255b..ab91543 100644
--- a/source/fuzz/fuzzer_pass_add_loads.cpp
+++ b/source/fuzz/fuzzer_pass_add_loads.cpp
@@ -23,11 +23,10 @@
 FuzzerPassAddLoads::FuzzerPassAddLoads(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddLoads::~FuzzerPassAddLoads() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddLoads::Apply() {
   ForEachInstructionWithInstructionDescriptor(
@@ -40,18 +39,22 @@
                "The opcode of the instruction we might insert before must be "
                "the same as the opcode in the descriptor for the instruction");
 
-        // Check whether it is legitimate to insert a load before this
-        // instruction.
-        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) {
-          return;
-        }
-
         // Randomly decide whether to try inserting a load here.
         if (!GetFuzzerContext()->ChoosePercentage(
                 GetFuzzerContext()->GetChanceOfAddingLoad())) {
           return;
         }
 
+        // Check whether it is legitimate to insert a load or atomic load before
+        // this instruction.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) {
+          return;
+        }
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAtomicLoad,
+                                                          inst_it)) {
+          return;
+        }
+
         std::vector<opt::Instruction*> relevant_instructions =
             FindAvailableInstructions(
                 function, block, inst_it,
@@ -81,13 +84,53 @@
           return;
         }
 
-        // Choose a pointer at random, and create and apply a loading
-        // transformation based on it.
-        ApplyTransformation(TransformationLoad(
-            GetFuzzerContext()->GetFreshId(),
+        auto chosen_instruction =
             relevant_instructions[GetFuzzerContext()->RandomIndex(
-                                      relevant_instructions)]
-                ->result_id(),
+                relevant_instructions)];
+
+        bool is_atomic_load = false;
+        uint32_t memory_scope_id = 0;
+        uint32_t memory_semantics_id = 0;
+
+        auto storage_class = static_cast<SpvStorageClass>(
+            GetIRContext()
+                ->get_def_use_mgr()
+                ->GetDef(chosen_instruction->type_id())
+                ->GetSingleWordInOperand(0));
+
+        switch (storage_class) {
+          case SpvStorageClassStorageBuffer:
+          case SpvStorageClassPhysicalStorageBuffer:
+          case SpvStorageClassWorkgroup:
+          case SpvStorageClassCrossWorkgroup:
+          case SpvStorageClassAtomicCounter:
+          case SpvStorageClassImage:
+            if (GetFuzzerContext()->ChoosePercentage(
+                    GetFuzzerContext()->GetChanceOfAddingAtomicLoad())) {
+              is_atomic_load = true;
+
+              memory_scope_id = FindOrCreateConstant(
+                  {SpvScopeInvocation},
+                  FindOrCreateIntegerType(32, GetFuzzerContext()->ChooseEven()),
+                  false);
+
+              memory_semantics_id = FindOrCreateConstant(
+                  {static_cast<uint32_t>(
+                      fuzzerutil::GetMemorySemanticsForStorageClass(
+                          storage_class))},
+                  FindOrCreateIntegerType(32, GetFuzzerContext()->ChooseEven()),
+                  false);
+            }
+            break;
+
+          default:
+            break;
+        }
+
+        // Create and apply the transformation.
+        ApplyTransformation(TransformationLoad(
+            GetFuzzerContext()->GetFreshId(), chosen_instruction->result_id(),
+            is_atomic_load, memory_scope_id, memory_semantics_id,
             instruction_descriptor));
       });
 }
diff --git a/source/fuzz/fuzzer_pass_add_loads.h b/source/fuzz/fuzzer_pass_add_loads.h
index c4d5b27..14460e7 100644
--- a/source/fuzz/fuzzer_pass_add_loads.h
+++ b/source/fuzz/fuzzer_pass_add_loads.h
@@ -26,9 +26,8 @@
   FuzzerPassAddLoads(opt::IRContext* ir_context,
                      TransformationContext* transformation_context,
                      FuzzerContext* fuzzer_context,
-                     protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddLoads();
+                     protobufs::TransformationSequence* transformations,
+                     bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.cpp b/source/fuzz/fuzzer_pass_add_local_variables.cpp
index ef8b5d0..a4e739f 100644
--- a/source/fuzz/fuzzer_pass_add_local_variables.cpp
+++ b/source/fuzz/fuzzer_pass_add_local_variables.cpp
@@ -24,11 +24,10 @@
 FuzzerPassAddLocalVariables::FuzzerPassAddLocalVariables(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddLocalVariables::~FuzzerPassAddLocalVariables() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddLocalVariables::Apply() {
   auto basic_type_ids_and_pointers =
@@ -36,6 +35,10 @@
 
   // These are the basic types that are available to this fuzzer pass.
   auto& basic_types = basic_type_ids_and_pointers.first;
+  if (basic_types.empty()) {
+    // The pass cannot do anything if there are no basic types.
+    return;
+  }
 
   // These are the pointers to those basic types that are *initially* available
   // to the fuzzer pass.  The fuzzer pass might add pointer types in cases where
diff --git a/source/fuzz/fuzzer_pass_add_local_variables.h b/source/fuzz/fuzzer_pass_add_local_variables.h
index 08d26d8..c969ba0 100644
--- a/source/fuzz/fuzzer_pass_add_local_variables.h
+++ b/source/fuzz/fuzzer_pass_add_local_variables.h
@@ -27,9 +27,8 @@
   FuzzerPassAddLocalVariables(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddLocalVariables();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp b/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp
index bdc3151..e8c9e96 100644
--- a/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp
+++ b/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp
@@ -23,11 +23,10 @@
 FuzzerPassAddLoopPreheaders::FuzzerPassAddLoopPreheaders(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddLoopPreheaders::~FuzzerPassAddLoopPreheaders() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddLoopPreheaders::Apply() {
   for (auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_add_loop_preheaders.h b/source/fuzz/fuzzer_pass_add_loop_preheaders.h
index a835056..2a13dba 100644
--- a/source/fuzz/fuzzer_pass_add_loop_preheaders.h
+++ b/source/fuzz/fuzzer_pass_add_loop_preheaders.h
@@ -30,9 +30,8 @@
   FuzzerPassAddLoopPreheaders(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddLoopPreheaders();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
index 31a5779..1a65ef6 100644
--- a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
@@ -29,12 +29,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddLoopsToCreateIntConstantSynonyms::
-    ~FuzzerPassAddLoopsToCreateIntConstantSynonyms() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddLoopsToCreateIntConstantSynonyms::Apply() {
   std::vector<uint32_t> constants;
diff --git a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
index ee98c4e..14e1754 100644
--- a/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
@@ -28,9 +28,8 @@
   FuzzerPassAddLoopsToCreateIntConstantSynonyms(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddLoopsToCreateIntConstantSynonyms();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp
index 09627d0..8ae6e90 100644
--- a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp
+++ b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.cpp
@@ -22,12 +22,10 @@
 FuzzerPassAddNoContractionDecorations::FuzzerPassAddNoContractionDecorations(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddNoContractionDecorations::
-    ~FuzzerPassAddNoContractionDecorations() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddNoContractionDecorations::Apply() {
   // Consider every instruction in every block in every function.
diff --git a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
index c0c1e09..7aeb26d 100644
--- a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
+++ b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
@@ -26,9 +26,8 @@
   FuzzerPassAddNoContractionDecorations(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddNoContractionDecorations() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
index 2b339ca..73b6b0a 100644
--- a/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
@@ -23,11 +23,10 @@
 FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddOpPhiSynonyms::Apply() {
   // Get a list of synonymous ids with the same type that can be used in the
diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.h b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
index 6c7d752..c45c9a8 100644
--- a/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
+++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
@@ -25,12 +25,11 @@
 // synonymous with the others.
 class FuzzerPassAddOpPhiSynonyms : public FuzzerPass {
  public:
-  FuzzerPassAddOpPhiSynonyms(
-      opt::IRContext* ir_context, TransformationContext* transformation_context,
-      FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddOpPhiSynonyms() override;
+  FuzzerPassAddOpPhiSynonyms(opt::IRContext* ir_context,
+                             TransformationContext* transformation_context,
+                             FuzzerContext* fuzzer_context,
+                             protobufs::TransformationSequence* transformations,
+                             bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_add_parameters.cpp b/source/fuzz/fuzzer_pass_add_parameters.cpp
index 35532e9..1cb6a79 100644
--- a/source/fuzz/fuzzer_pass_add_parameters.cpp
+++ b/source/fuzz/fuzzer_pass_add_parameters.cpp
@@ -25,11 +25,10 @@
 FuzzerPassAddParameters::FuzzerPassAddParameters(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddParameters::~FuzzerPassAddParameters() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddParameters::Apply() {
   // Compute type candidates for the new parameter.
diff --git a/source/fuzz/fuzzer_pass_add_parameters.h b/source/fuzz/fuzzer_pass_add_parameters.h
index f1261ae..c79f0e0 100644
--- a/source/fuzz/fuzzer_pass_add_parameters.h
+++ b/source/fuzz/fuzzer_pass_add_parameters.h
@@ -30,9 +30,8 @@
   FuzzerPassAddParameters(opt::IRContext* ir_context,
                           TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
-                          protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddParameters() override;
+                          protobufs::TransformationSequence* transformations,
+                          bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_add_relaxed_decorations.cpp b/source/fuzz/fuzzer_pass_add_relaxed_decorations.cpp
index a2497df..b8c2b55 100644
--- a/source/fuzz/fuzzer_pass_add_relaxed_decorations.cpp
+++ b/source/fuzz/fuzzer_pass_add_relaxed_decorations.cpp
@@ -22,11 +22,10 @@
 FuzzerPassAddRelaxedDecorations::FuzzerPassAddRelaxedDecorations(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddRelaxedDecorations::~FuzzerPassAddRelaxedDecorations() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddRelaxedDecorations::Apply() {
   // Consider every instruction in every block in every function.
diff --git a/source/fuzz/fuzzer_pass_add_relaxed_decorations.h b/source/fuzz/fuzzer_pass_add_relaxed_decorations.h
index 897b821..51eb594 100644
--- a/source/fuzz/fuzzer_pass_add_relaxed_decorations.h
+++ b/source/fuzz/fuzzer_pass_add_relaxed_decorations.h
@@ -26,9 +26,8 @@
   FuzzerPassAddRelaxedDecorations(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddRelaxedDecorations() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_stores.cpp b/source/fuzz/fuzzer_pass_add_stores.cpp
index 46efc64..606e4a6 100644
--- a/source/fuzz/fuzzer_pass_add_stores.cpp
+++ b/source/fuzz/fuzzer_pass_add_stores.cpp
@@ -23,11 +23,10 @@
 FuzzerPassAddStores::FuzzerPassAddStores(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddStores::~FuzzerPassAddStores() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddStores::Apply() {
   ForEachInstructionWithInstructionDescriptor(
@@ -40,16 +39,20 @@
                "The opcode of the instruction we might insert before must be "
                "the same as the opcode in the descriptor for the instruction");
 
+        // Randomly decide whether to try inserting a store here.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingStore())) {
+          return;
+        }
+
         // Check whether it is legitimate to insert a store before this
         // instruction.
         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore,
                                                           inst_it)) {
           return;
         }
-
-        // Randomly decide whether to try inserting a store here.
-        if (!GetFuzzerContext()->ChoosePercentage(
-                GetFuzzerContext()->GetChanceOfAddingStore())) {
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAtomicStore,
+                                                          inst_it)) {
           return;
         }
 
@@ -118,10 +121,49 @@
           return;
         }
 
-        // Choose a value at random, and create and apply a storing
-        // transformation based on it and the pointer.
+        bool is_atomic_store = false;
+        uint32_t memory_scope_id = 0;
+        uint32_t memory_semantics_id = 0;
+
+        auto storage_class =
+            static_cast<SpvStorageClass>(GetIRContext()
+                                             ->get_def_use_mgr()
+                                             ->GetDef(pointer->type_id())
+                                             ->GetSingleWordInOperand(0));
+
+        switch (storage_class) {
+          case SpvStorageClassStorageBuffer:
+          case SpvStorageClassPhysicalStorageBuffer:
+          case SpvStorageClassWorkgroup:
+          case SpvStorageClassCrossWorkgroup:
+          case SpvStorageClassAtomicCounter:
+          case SpvStorageClassImage:
+            if (GetFuzzerContext()->ChoosePercentage(
+                    GetFuzzerContext()->GetChanceOfAddingAtomicStore())) {
+              is_atomic_store = true;
+
+              memory_scope_id = FindOrCreateConstant(
+                  {SpvScopeInvocation},
+                  FindOrCreateIntegerType(32, GetFuzzerContext()->ChooseEven()),
+                  false);
+
+              memory_semantics_id = FindOrCreateConstant(
+                  {static_cast<uint32_t>(
+                      fuzzerutil::GetMemorySemanticsForStorageClass(
+                          storage_class))},
+                  FindOrCreateIntegerType(32, GetFuzzerContext()->ChooseEven()),
+                  false);
+            }
+            break;
+
+          default:
+            break;
+        }
+
+        // Create and apply the transformation.
         ApplyTransformation(TransformationStore(
-            pointer->result_id(),
+            pointer->result_id(), is_atomic_store, memory_scope_id,
+            memory_semantics_id,
             relevant_values[GetFuzzerContext()->RandomIndex(relevant_values)]
                 ->result_id(),
             instruction_descriptor));
diff --git a/source/fuzz/fuzzer_pass_add_stores.h b/source/fuzz/fuzzer_pass_add_stores.h
index 55ec67f..b18dde1 100644
--- a/source/fuzz/fuzzer_pass_add_stores.h
+++ b/source/fuzz/fuzzer_pass_add_stores.h
@@ -28,9 +28,8 @@
   FuzzerPassAddStores(opt::IRContext* ir_context,
                       TransformationContext* transformation_context,
                       FuzzerContext* fuzzer_context,
-                      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddStores();
+                      protobufs::TransformationSequence* transformations,
+                      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_synonyms.cpp b/source/fuzz/fuzzer_pass_add_synonyms.cpp
index 2fa0700..1d188de 100644
--- a/source/fuzz/fuzzer_pass_add_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_add_synonyms.cpp
@@ -25,11 +25,10 @@
 FuzzerPassAddSynonyms::FuzzerPassAddSynonyms(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddSynonyms::~FuzzerPassAddSynonyms() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddSynonyms::Apply() {
   ForEachInstructionWithInstructionDescriptor(
@@ -81,6 +80,8 @@
           case protobufs::TransformationAddSynonym::ADD_ZERO:
           case protobufs::TransformationAddSynonym::SUB_ZERO:
           case protobufs::TransformationAddSynonym::LOGICAL_OR:
+          case protobufs::TransformationAddSynonym::BITWISE_OR:
+          case protobufs::TransformationAddSynonym::BITWISE_XOR:
             // Create a zero constant to be used as an operand of the synonymous
             // instruction.
             FindOrCreateZeroConstant(existing_synonym->type_id(), false);
diff --git a/source/fuzz/fuzzer_pass_add_synonyms.h b/source/fuzz/fuzzer_pass_add_synonyms.h
index dcfb938..b0c174b 100644
--- a/source/fuzz/fuzzer_pass_add_synonyms.h
+++ b/source/fuzz/fuzzer_pass_add_synonyms.h
@@ -27,9 +27,8 @@
   FuzzerPassAddSynonyms(opt::IRContext* ir_context,
                         TransformationContext* transformation_context,
                         FuzzerContext* fuzzer_context,
-                        protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddSynonyms() override;
+                        protobufs::TransformationSequence* transformations,
+                        bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.cpp b/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.cpp
index 453448b..a29d1d3 100644
--- a/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.cpp
+++ b/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.cpp
@@ -24,12 +24,10 @@
 FuzzerPassAddVectorShuffleInstructions::FuzzerPassAddVectorShuffleInstructions(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAddVectorShuffleInstructions::
-    ~FuzzerPassAddVectorShuffleInstructions() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAddVectorShuffleInstructions::Apply() {
   ForEachInstructionWithInstructionDescriptor(
@@ -78,7 +76,7 @@
                            ->IdIsIrrelevant(instruction->result_id()) &&
                       !fuzzerutil::CanMakeSynonymOf(ir_context,
                                                     *GetTransformationContext(),
-                                                    instruction)) {
+                                                    *instruction)) {
                     // If the id is irrelevant, we can use it since it will not
                     // participate in DataSynonym fact. Otherwise, we should be
                     // able to produce a synonym out of the id.
diff --git a/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h b/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h
index 99b9f24..64d6608 100644
--- a/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h
+++ b/source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h
@@ -26,9 +26,8 @@
   FuzzerPassAddVectorShuffleInstructions(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAddVectorShuffleInstructions();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_adjust_branch_weights.cpp b/source/fuzz/fuzzer_pass_adjust_branch_weights.cpp
index 1d6d434..94428f7 100644
--- a/source/fuzz/fuzzer_pass_adjust_branch_weights.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_branch_weights.cpp
@@ -24,11 +24,10 @@
 FuzzerPassAdjustBranchWeights::FuzzerPassAdjustBranchWeights(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAdjustBranchWeights::~FuzzerPassAdjustBranchWeights() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAdjustBranchWeights::Apply() {
   // For all OpBranchConditional instructions,
diff --git a/source/fuzz/fuzzer_pass_adjust_branch_weights.h b/source/fuzz/fuzzer_pass_adjust_branch_weights.h
index 5b2b33f..ae1ea34 100644
--- a/source/fuzz/fuzzer_pass_adjust_branch_weights.h
+++ b/source/fuzz/fuzzer_pass_adjust_branch_weights.h
@@ -28,9 +28,8 @@
   FuzzerPassAdjustBranchWeights(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAdjustBranchWeights();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.cpp b/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
index aa62d2f..1c2bc8c 100644
--- a/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_function_controls.cpp
@@ -22,11 +22,10 @@
 FuzzerPassAdjustFunctionControls::FuzzerPassAdjustFunctionControls(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAdjustFunctionControls::~FuzzerPassAdjustFunctionControls() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAdjustFunctionControls::Apply() {
   // Consider every function in the module.
diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.h b/source/fuzz/fuzzer_pass_adjust_function_controls.h
index 5fdbe43..7a8c492 100644
--- a/source/fuzz/fuzzer_pass_adjust_function_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_function_controls.h
@@ -26,9 +26,8 @@
   FuzzerPassAdjustFunctionControls(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAdjustFunctionControls() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
index f7addff..fe855ca 100644
--- a/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.cpp
@@ -22,11 +22,10 @@
 FuzzerPassAdjustLoopControls::FuzzerPassAdjustLoopControls(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAdjustLoopControls::~FuzzerPassAdjustLoopControls() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAdjustLoopControls::Apply() {
   // Consider every merge instruction in the module (via looking through all
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.h b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
index f133b2d..25e8304 100644
--- a/source/fuzz/fuzzer_pass_adjust_loop_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
@@ -26,9 +26,8 @@
   FuzzerPassAdjustLoopControls(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAdjustLoopControls() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
index 68f0ca7..d2ff40e 100644
--- a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.cpp
@@ -23,12 +23,10 @@
 FuzzerPassAdjustMemoryOperandsMasks::FuzzerPassAdjustMemoryOperandsMasks(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAdjustMemoryOperandsMasks::~FuzzerPassAdjustMemoryOperandsMasks() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAdjustMemoryOperandsMasks::Apply() {
   // Consider every block in every function.
diff --git a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
index 699dcb5..3197f91 100644
--- a/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
+++ b/source/fuzz/fuzzer_pass_adjust_memory_operands_masks.h
@@ -27,9 +27,8 @@
   FuzzerPassAdjustMemoryOperandsMasks(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAdjustMemoryOperandsMasks();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp b/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
index 83b1854..7d8e6b5 100644
--- a/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
+++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.cpp
@@ -22,12 +22,10 @@
 FuzzerPassAdjustSelectionControls::FuzzerPassAdjustSelectionControls(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassAdjustSelectionControls::~FuzzerPassAdjustSelectionControls() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassAdjustSelectionControls::Apply() {
   // Consider every merge instruction in the module (via looking through all
diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.h b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
index 910b40d..ac55de7 100644
--- a/source/fuzz/fuzzer_pass_adjust_selection_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
@@ -26,9 +26,8 @@
   FuzzerPassAdjustSelectionControls(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassAdjustSelectionControls() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
index 38553d2..5c3b86b 100644
--- a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
@@ -28,11 +28,10 @@
 FuzzerPassApplyIdSynonyms::FuzzerPassApplyIdSynonyms(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassApplyIdSynonyms::~FuzzerPassApplyIdSynonyms() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassApplyIdSynonyms::Apply() {
   // Compute a closure of data synonym facts, to enrich the pool of synonyms
@@ -199,7 +198,7 @@
       GetIRContext(), base_object_type_id_2, dd2.index());
   assert(type_id_1 && type_id_2 && "Data descriptors have invalid types");
 
-  return TransformationReplaceIdWithSynonym::TypesAreCompatible(
+  return fuzzerutil::TypesAreCompatible(
       GetIRContext(), opcode, use_in_operand_index, type_id_1, type_id_2);
 }
 
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.h b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
index 5deac10..3da9c5d 100644
--- a/source/fuzz/fuzzer_pass_apply_id_synonyms.h
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
@@ -28,9 +28,8 @@
   FuzzerPassApplyIdSynonyms(opt::IRContext* ir_context,
                             TransformationContext* transformation_context,
                             FuzzerContext* fuzzer_context,
-                            protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassApplyIdSynonyms() override;
+                            protobufs::TransformationSequence* transformations,
+                            bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_construct_composites.cpp b/source/fuzz/fuzzer_pass_construct_composites.cpp
index 584fa1a..ff022fc 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.cpp
+++ b/source/fuzz/fuzzer_pass_construct_composites.cpp
@@ -16,6 +16,7 @@
 
 #include <memory>
 
+#include "source/fuzz/available_instructions.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/transformation_composite_construct.h"
 
@@ -25,11 +26,10 @@
 FuzzerPassConstructComposites::FuzzerPassConstructComposites(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassConstructComposites::Apply() {
   // Gather up the ids of all composite types, but skip block-/buffer
@@ -44,12 +44,40 @@
     }
   }
 
+  if (composite_type_ids.empty()) {
+    // There are no composite types, so this fuzzer pass cannot do anything.
+    return;
+  }
+
+  AvailableInstructions available_composite_constituents(
+      GetIRContext(),
+      [this](opt::IRContext* ir_context, opt::Instruction* inst) -> bool {
+        if (!inst->result_id() || !inst->type_id()) {
+          return false;
+        }
+
+        // If the id is irrelevant, we can use it since it will not
+        // participate in DataSynonym fact. Otherwise, we should be able
+        // to produce a synonym out of the id.
+        return GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+                   inst->result_id()) ||
+               fuzzerutil::CanMakeSynonymOf(ir_context,
+                                            *GetTransformationContext(), *inst);
+      });
+
   ForEachInstructionWithInstructionDescriptor(
-      [this, &composite_type_ids](
-          opt::Function* function, opt::BasicBlock* block,
+      [this, &available_composite_constituents, &composite_type_ids](
+          opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
           opt::BasicBlock::iterator inst_it,
           const protobufs::InstructionDescriptor& instruction_descriptor)
           -> void {
+        // Randomly decide whether to try inserting a composite construction
+        // here.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfConstructingComposite())) {
+          return;
+        }
+
         // Check whether it is legitimate to insert a composite construction
         // before the instruction.
         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
@@ -57,36 +85,21 @@
           return;
         }
 
-        // Randomly decide whether to try inserting an object copy here.
-        if (!GetFuzzerContext()->ChoosePercentage(
-                GetFuzzerContext()->GetChanceOfConstructingComposite())) {
-          return;
-        }
-
         // For each instruction that is available at this program point (i.e. an
         // instruction that is global or whose definition strictly dominates the
         // program point) and suitable for making a synonym of, associate it
         // with the id of its result type.
         TypeIdToInstructions type_id_to_available_instructions;
-        auto available_instructions = FindAvailableInstructions(
-            function, block, inst_it,
-            [this](opt::IRContext* ir_context, opt::Instruction* inst) {
-              if (!inst->result_id() || !inst->type_id()) {
-                return false;
-              }
-
-              // If the id is irrelevant, we can use it since it will not
-              // participate in DataSynonym fact. Otherwise, we should be able
-              // to produce a synonym out of the id.
-              return GetTransformationContext()
-                         ->GetFactManager()
-                         ->IdIsIrrelevant(inst->result_id()) ||
-                     fuzzerutil::CanMakeSynonymOf(
-                         ir_context, *GetTransformationContext(), inst);
-            });
-        for (auto instruction : available_instructions) {
-          RecordAvailableInstruction(instruction,
-                                     &type_id_to_available_instructions);
+        auto available_instructions =
+            available_composite_constituents.GetAvailableBeforeInstruction(
+                &*inst_it);
+        for (uint32_t available_instruction_index = 0;
+             available_instruction_index < available_instructions.size();
+             available_instruction_index++) {
+          opt::Instruction* inst =
+              available_instructions[available_instruction_index];
+          type_id_to_available_instructions[inst->type_id()].push_back(
+              inst->result_id());
         }
 
         // At this point, |composite_type_ids| captures all the composite types
@@ -94,68 +107,41 @@
         // captures all the available result ids we might use, organized by
         // type.
 
-        // Now we try to find a composite that we can construct.  We might not
-        // manage, if there is a paucity of available ingredients in the module
-        // (e.g. if our only available composite was a boolean vector and we had
-        // no instructions generating boolean result types available).
-        //
-        // If we succeed, |chosen_composite_type| will end up being non-zero,
-        // and |constructor_arguments| will end up giving us result ids suitable
-        // for constructing a composite of that type.  Otherwise these variables
-        // will remain 0 and null respectively.
-        uint32_t chosen_composite_type = 0;
+        // Now we choose a composite type to construct, building it from
+        // available constituent components and using zero constants if suitable
+        // components are not available.
+
         std::vector<uint32_t> constructor_arguments;
+        uint32_t chosen_composite_type =
+            composite_type_ids[GetFuzzerContext()->RandomIndex(
+                composite_type_ids)];
 
-        // Initially, all composite type ids are available for us to try.  Keep
-        // trying until we run out of options.
-        auto composites_to_try_constructing = composite_type_ids;
-        while (!composites_to_try_constructing.empty()) {
-          // Remove a composite type from the composite types left for us to
-          // try.
-          auto next_composite_to_try_constructing =
-              GetFuzzerContext()->RemoveAtRandomIndex(
-                  &composites_to_try_constructing);
-
-          // Now try to construct a composite of this type, using an appropriate
-          // helper method depending on the kind of composite type.
-          auto composite_type_inst = GetIRContext()->get_def_use_mgr()->GetDef(
-              next_composite_to_try_constructing);
-          switch (composite_type_inst->opcode()) {
-            case SpvOpTypeArray:
-              constructor_arguments = FindComponentsToConstructArray(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            case SpvOpTypeMatrix:
-              constructor_arguments = FindComponentsToConstructMatrix(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            case SpvOpTypeStruct:
-              constructor_arguments = FindComponentsToConstructStruct(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            case SpvOpTypeVector:
-              constructor_arguments = FindComponentsToConstructVector(
-                  *composite_type_inst, type_id_to_available_instructions);
-              break;
-            default:
-              assert(false &&
-                     "The space of possible composite types should be covered "
-                     "by the above cases.");
-              break;
-          }
-          if (!constructor_arguments.empty()) {
-            // We succeeded!  Note the composite type we finally settled on, and
-            // exit from the loop.
-            chosen_composite_type = next_composite_to_try_constructing;
+        // Construct a composite of this type, using an appropriate helper
+        // method depending on the kind of composite type.
+        auto composite_type_inst =
+            GetIRContext()->get_def_use_mgr()->GetDef(chosen_composite_type);
+        switch (composite_type_inst->opcode()) {
+          case SpvOpTypeArray:
+            constructor_arguments = FindComponentsToConstructArray(
+                *composite_type_inst, type_id_to_available_instructions);
             break;
-          }
-        }
-
-        if (!chosen_composite_type) {
-          // We did not manage to make a composite; return 0 to indicate that no
-          // instructions were added.
-          assert(constructor_arguments.empty());
-          return;
+          case SpvOpTypeMatrix:
+            constructor_arguments = FindComponentsToConstructMatrix(
+                *composite_type_inst, type_id_to_available_instructions);
+            break;
+          case SpvOpTypeStruct:
+            constructor_arguments = FindComponentsToConstructStruct(
+                *composite_type_inst, type_id_to_available_instructions);
+            break;
+          case SpvOpTypeVector:
+            constructor_arguments = FindComponentsToConstructVector(
+                *composite_type_inst, type_id_to_available_instructions);
+            break;
+          default:
+            assert(false &&
+                   "The space of possible composite types should be covered "
+                   "by the above cases.");
+            break;
         }
         assert(!constructor_arguments.empty());
 
@@ -166,15 +152,6 @@
       });
 }
 
-void FuzzerPassConstructComposites::RecordAvailableInstruction(
-    opt::Instruction* inst,
-    TypeIdToInstructions* type_id_to_available_instructions) {
-  if (type_id_to_available_instructions->count(inst->type_id()) == 0) {
-    (*type_id_to_available_instructions)[inst->type_id()] = {};
-  }
-  type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
-}
-
 std::vector<uint32_t>
 FuzzerPassConstructComposites::FindComponentsToConstructArray(
     const opt::Instruction& array_type_instruction,
@@ -190,13 +167,6 @@
   auto available_instructions =
       type_id_to_available_instructions.find(element_type_id);
 
-  if (available_instructions == type_id_to_available_instructions.cend()) {
-    // If there are not any instructions available that compute the element type
-    // of the array then we are not in a position to construct a composite with
-    // this array type.
-    return {};
-  }
-
   uint32_t array_length =
       GetIRContext()
           ->get_def_use_mgr()
@@ -205,10 +175,14 @@
 
   std::vector<uint32_t> result;
   for (uint32_t index = 0; index < array_length; index++) {
-    result.push_back(available_instructions
-                         ->second[GetFuzzerContext()->RandomIndex(
-                             available_instructions->second)]
-                         ->result_id());
+    if (available_instructions == type_id_to_available_instructions.cend()) {
+      // No suitable instructions are available, so use a zero constant
+      result.push_back(FindOrCreateZeroConstant(element_type_id, true));
+    } else {
+      result.push_back(
+          available_instructions->second[GetFuzzerContext()->RandomIndex(
+              available_instructions->second)]);
+    }
   }
   return result;
 }
@@ -228,19 +202,17 @@
   auto available_instructions =
       type_id_to_available_instructions.find(element_type_id);
 
-  if (available_instructions == type_id_to_available_instructions.cend()) {
-    // If there are not any instructions available that compute the element type
-    // of the matrix then we are not in a position to construct a composite with
-    // this matrix type.
-    return {};
-  }
   std::vector<uint32_t> result;
   for (uint32_t index = 0;
        index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
-    result.push_back(available_instructions
-                         ->second[GetFuzzerContext()->RandomIndex(
-                             available_instructions->second)]
-                         ->result_id());
+    if (available_instructions == type_id_to_available_instructions.cend()) {
+      // No suitable components are available, so use a zero constant.
+      result.push_back(FindOrCreateZeroConstant(element_type_id, true));
+    } else {
+      result.push_back(
+          available_instructions->second[GetFuzzerContext()->RandomIndex(
+              available_instructions->second)]);
+    }
   }
   return result;
 }
@@ -263,14 +235,14 @@
     auto available_instructions =
         type_id_to_available_instructions.find(element_type_id);
     if (available_instructions == type_id_to_available_instructions.cend()) {
-      // If there are no such instructions, we cannot construct a composite of
-      // this struct type.
-      return {};
+      // No suitable component is available for this element type, so use a zero
+      // constant.
+      result.push_back(FindOrCreateZeroConstant(element_type_id, true));
+    } else {
+      result.push_back(
+          available_instructions->second[GetFuzzerContext()->RandomIndex(
+              available_instructions->second)]);
     }
-    result.push_back(available_instructions
-                         ->second[GetFuzzerContext()->RandomIndex(
-                             available_instructions->second)]
-                         ->result_id());
   }
   return result;
 }
@@ -325,12 +297,13 @@
   // (otherwise there will not be space left for a vec3).
 
   uint32_t vector_slots_used = 0;
-  // The instructions we will use to construct the vector, in no particular
-  // order at this stage.
-  std::vector<opt::Instruction*> instructions_to_use;
+
+  // The instructions result ids we will use to construct the vector, in no
+  // particular order at this stage.
+  std::vector<uint32_t> result;
 
   while (vector_slots_used < element_count) {
-    std::vector<opt::Instruction*> instructions_to_choose_from;
+    std::vector<uint32_t> instructions_to_choose_from;
     for (auto& entry : smaller_vector_type_id_to_width) {
       if (entry.second >
           std::min(element_count - 1, element_count - vector_slots_used)) {
@@ -345,19 +318,16 @@
                                          available_instructions->second.begin(),
                                          available_instructions->second.end());
     }
-    if (instructions_to_choose_from.empty()) {
-      // We may get unlucky and find that there are not any instructions to
-      // choose from.  In this case we give up constructing a composite of this
-      // vector type.  It might be that we could construct the composite in
-      // another manner, so we could opt to retry a few times here, but it is
-      // simpler to just give up on the basis that this will not happen
-      // frequently.
-      return {};
-    }
-    auto instruction_to_use =
-        instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
-            instructions_to_choose_from)];
-    instructions_to_use.push_back(instruction_to_use);
+    // If there are no instructions to choose from then use a zero constant,
+    // otherwise select one of the instructions at random.
+    uint32_t id_of_instruction_to_use =
+        instructions_to_choose_from.empty()
+            ? FindOrCreateZeroConstant(element_type_id, true)
+            : instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
+                  instructions_to_choose_from)];
+    opt::Instruction* instruction_to_use =
+        GetIRContext()->get_def_use_mgr()->GetDef(id_of_instruction_to_use);
+    result.push_back(instruction_to_use->result_id());
     auto chosen_type =
         GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
     if (chosen_type->AsVector()) {
@@ -373,14 +343,7 @@
   }
   assert(vector_slots_used == element_count);
 
-  std::vector<uint32_t> result;
-  std::vector<uint32_t> operands;
-  while (!instructions_to_use.empty()) {
-    auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
-    result.push_back(instructions_to_use[index]->result_id());
-    instructions_to_use.erase(instructions_to_use.begin() + index);
-  }
-  assert(result.size() > 1);
+  GetFuzzerContext()->Shuffle(&result);
   return result;
 }
 
diff --git a/source/fuzz/fuzzer_pass_construct_composites.h b/source/fuzz/fuzzer_pass_construct_composites.h
index c140bde..8141398 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.h
+++ b/source/fuzz/fuzzer_pass_construct_composites.h
@@ -15,7 +15,7 @@
 #ifndef SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
 #define SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
 
-#include <map>
+#include <unordered_map>
 #include <vector>
 
 #include "source/fuzz/fuzzer_pass.h"
@@ -29,25 +29,15 @@
   FuzzerPassConstructComposites(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassConstructComposites();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
  private:
-  // Used to map a type id to relevant instructions whose result type matches
-  // the type id.
-  typedef std::map<uint32_t, std::vector<opt::Instruction*>>
-      TypeIdToInstructions;
-
-  // Considers all instructions that are available at |inst| - instructions
-  // whose results could be packed into a composite - and updates
-  // |type_id_to_available_instructions| so that each such instruction is
-  // associated with its the id of its result type.
-  void RecordAvailableInstruction(
-      opt::Instruction* inst,
-      TypeIdToInstructions* type_id_to_available_instructions);
+  // Used to map a type id to the ids of relevant instructions of the type.
+  using TypeIdToInstructions =
+      std::unordered_map<uint32_t, std::vector<uint32_t>>;
 
   // Requires that |array_type_instruction| has opcode OpTypeArray.
   // Attempts to find suitable instruction result ids from the values of
diff --git a/source/fuzz/fuzzer_pass_copy_objects.cpp b/source/fuzz/fuzzer_pass_copy_objects.cpp
index 9f7bbd6..80cc2a5 100644
--- a/source/fuzz/fuzzer_pass_copy_objects.cpp
+++ b/source/fuzz/fuzzer_pass_copy_objects.cpp
@@ -24,11 +24,10 @@
 FuzzerPassCopyObjects::FuzzerPassCopyObjects(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassCopyObjects::~FuzzerPassCopyObjects() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassCopyObjects::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_copy_objects.h b/source/fuzz/fuzzer_pass_copy_objects.h
index 8de382e..2fb5a53 100644
--- a/source/fuzz/fuzzer_pass_copy_objects.h
+++ b/source/fuzz/fuzzer_pass_copy_objects.h
@@ -26,9 +26,8 @@
   FuzzerPassCopyObjects(opt::IRContext* ir_context,
                         TransformationContext* transformation_context,
                         FuzzerContext* fuzzer_context,
-                        protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassCopyObjects();
+                        protobufs::TransformationSequence* transformations,
+                        bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp
index 4cd4804..5bdf697 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.cpp
+++ b/source/fuzz/fuzzer_pass_donate_modules.cpp
@@ -45,12 +45,11 @@
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
     protobufs::TransformationSequence* transformations,
-    const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers)
+    bool ignore_inapplicable_transformations,
+    std::vector<fuzzerutil::ModuleSupplier> donor_suppliers)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations),
-      donor_suppliers_(donor_suppliers) {}
-
-FuzzerPassDonateModules::~FuzzerPassDonateModules() = default;
+                 transformations, ignore_inapplicable_transformations),
+      donor_suppliers_(std::move(donor_suppliers)) {}
 
 void FuzzerPassDonateModules::Apply() {
   // If there are no donor suppliers, this fuzzer pass is a no-op.
@@ -1202,11 +1201,14 @@
         false);
   }
 
-  // Add the function in a livesafe manner.
-  ApplyTransformation(TransformationAddFunction(
+  // Try to add the function in a livesafe manner. This may fail due to edge
+  // cases, e.g. where adding loop limiters changes dominance such that the
+  // module becomes invalid. It would be ideal to handle all such edge cases,
+  // but as they are rare it is more pragmatic to bail out of making the
+  // function livesafe if the transformation's precondition fails to hold.
+  return MaybeApplyTransformation(TransformationAddFunction(
       donated_instructions, loop_limiter_variable_id, loop_limit, loop_limiters,
       kill_unreachable_return_value_id, access_chain_clamping_info));
-  return true;
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_donate_modules.h b/source/fuzz/fuzzer_pass_donate_modules.h
index 0424cec..924dd35 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.h
+++ b/source/fuzz/fuzzer_pass_donate_modules.h
@@ -31,9 +31,8 @@
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations,
-      const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers);
-
-  ~FuzzerPassDonateModules();
+      bool ignore_inapplicable_transformations,
+      std::vector<fuzzerutil::ModuleSupplier> donor_suppliers);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
index 1651a9d..3a9a7e6 100644
--- a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
+++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
@@ -25,12 +25,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassDuplicateRegionsWithSelections::
-    ~FuzzerPassDuplicateRegionsWithSelections() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassDuplicateRegionsWithSelections::Apply() {
   // Iterate over all of the functions in the module.
diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
index 3fae698..74343a6 100644
--- a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
+++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
@@ -29,9 +29,8 @@
   FuzzerPassDuplicateRegionsWithSelections(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassDuplicateRegionsWithSelections() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp b/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp
index 1416fe0..5bf0461 100644
--- a/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp
+++ b/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp
@@ -24,11 +24,10 @@
 FuzzerPassExpandVectorReductions::FuzzerPassExpandVectorReductions(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassExpandVectorReductions::~FuzzerPassExpandVectorReductions() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassExpandVectorReductions::Apply() {
   for (auto& function : *GetIRContext()->module()) {
@@ -48,7 +47,7 @@
 
         // It must be able to make a synonym of |instruction|.
         if (!fuzzerutil::CanMakeSynonymOf(
-                GetIRContext(), *GetTransformationContext(), &instruction)) {
+                GetIRContext(), *GetTransformationContext(), instruction)) {
           continue;
         }
 
diff --git a/source/fuzz/fuzzer_pass_expand_vector_reductions.h b/source/fuzz/fuzzer_pass_expand_vector_reductions.h
index ae3238b..c0673e1 100644
--- a/source/fuzz/fuzzer_pass_expand_vector_reductions.h
+++ b/source/fuzz/fuzzer_pass_expand_vector_reductions.h
@@ -28,9 +28,8 @@
   FuzzerPassExpandVectorReductions(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassExpandVectorReductions();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp b/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
index 1e21aa5..70fa6a1 100644
--- a/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
+++ b/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
@@ -26,12 +26,10 @@
 FuzzerPassFlattenConditionalBranches::FuzzerPassFlattenConditionalBranches(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassFlattenConditionalBranches::~FuzzerPassFlattenConditionalBranches() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassFlattenConditionalBranches::Apply() {
   for (auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_flatten_conditional_branches.h b/source/fuzz/fuzzer_pass_flatten_conditional_branches.h
index 76f7782..abfbc3c 100644
--- a/source/fuzz/fuzzer_pass_flatten_conditional_branches.h
+++ b/source/fuzz/fuzzer_pass_flatten_conditional_branches.h
@@ -25,9 +25,8 @@
   FuzzerPassFlattenConditionalBranches(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassFlattenConditionalBranches() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_inline_functions.cpp b/source/fuzz/fuzzer_pass_inline_functions.cpp
index 90160d8..4024096 100644
--- a/source/fuzz/fuzzer_pass_inline_functions.cpp
+++ b/source/fuzz/fuzzer_pass_inline_functions.cpp
@@ -25,11 +25,10 @@
 FuzzerPassInlineFunctions::FuzzerPassInlineFunctions(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassInlineFunctions::Apply() {
   // |function_call_instructions| are the instructions that will be inlined.
diff --git a/source/fuzz/fuzzer_pass_inline_functions.h b/source/fuzz/fuzzer_pass_inline_functions.h
index 37295d1..c4e0b83 100644
--- a/source/fuzz/fuzzer_pass_inline_functions.h
+++ b/source/fuzz/fuzzer_pass_inline_functions.h
@@ -28,9 +28,8 @@
   FuzzerPassInlineFunctions(opt::IRContext* ir_context,
                             TransformationContext* transformation_context,
                             FuzzerContext* fuzzer_context,
-                            protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassInlineFunctions() override;
+                            protobufs::TransformationSequence* transformations,
+                            bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp
index 0e40b49..d8780ff 100644
--- a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp
+++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp
@@ -27,14 +27,15 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassInterchangeSignednessOfIntegerOperands::
-    ~FuzzerPassInterchangeSignednessOfIntegerOperands() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassInterchangeSignednessOfIntegerOperands::Apply() {
+  assert(!GetFuzzerContext()->IsWgslCompatible() &&
+         "Cannot interchange signedness in WGSL");
+
   // Make vector keeping track of all the uses we want to replace.
   // This is a vector of pairs, where the first element is an id use descriptor
   // identifying the use of a constant id and the second is the id that should
diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h
index 06882f4..6a10e89 100644
--- a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h
+++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h
@@ -32,9 +32,8 @@
   FuzzerPassInterchangeSignednessOfIntegerOperands(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassInterchangeSignednessOfIntegerOperands() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
index 20575e1..2c16cd5 100644
--- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
+++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
@@ -25,12 +25,10 @@
 FuzzerPassInterchangeZeroLikeConstants::FuzzerPassInterchangeZeroLikeConstants(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassInterchangeZeroLikeConstants::
-    ~FuzzerPassInterchangeZeroLikeConstants() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant(
     opt::Instruction* declaration) {
diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
index ef0f765..bb4a4dc 100644
--- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
+++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
@@ -33,9 +33,8 @@
   FuzzerPassInterchangeZeroLikeConstants(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassInterchangeZeroLikeConstants() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_invert_comparison_operators.cpp b/source/fuzz/fuzzer_pass_invert_comparison_operators.cpp
index de4ff1d..130f750 100644
--- a/source/fuzz/fuzzer_pass_invert_comparison_operators.cpp
+++ b/source/fuzz/fuzzer_pass_invert_comparison_operators.cpp
@@ -24,12 +24,10 @@
 FuzzerPassInvertComparisonOperators::FuzzerPassInvertComparisonOperators(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassInvertComparisonOperators::~FuzzerPassInvertComparisonOperators() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassInvertComparisonOperators::Apply() {
   GetIRContext()->module()->ForEachInst([this](const opt::Instruction* inst) {
diff --git a/source/fuzz/fuzzer_pass_invert_comparison_operators.h b/source/fuzz/fuzzer_pass_invert_comparison_operators.h
index 9c80bbb..d0d09ed 100644
--- a/source/fuzz/fuzzer_pass_invert_comparison_operators.h
+++ b/source/fuzz/fuzzer_pass_invert_comparison_operators.h
@@ -27,9 +27,8 @@
   FuzzerPassInvertComparisonOperators(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassInvertComparisonOperators() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp
index f4f2a80..b755d23 100644
--- a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp
+++ b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp
@@ -24,12 +24,10 @@
 FuzzerPassMakeVectorOperationsDynamic::FuzzerPassMakeVectorOperationsDynamic(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassMakeVectorOperationsDynamic::
-    ~FuzzerPassMakeVectorOperationsDynamic() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassMakeVectorOperationsDynamic::Apply() {
   for (auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h
index dd51cde..b6a46c7 100644
--- a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h
+++ b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h
@@ -27,9 +27,8 @@
   FuzzerPassMakeVectorOperationsDynamic(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassMakeVectorOperationsDynamic() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_merge_blocks.cpp b/source/fuzz/fuzzer_pass_merge_blocks.cpp
index e66fc44..7020062 100644
--- a/source/fuzz/fuzzer_pass_merge_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_merge_blocks.cpp
@@ -24,11 +24,10 @@
 FuzzerPassMergeBlocks::FuzzerPassMergeBlocks(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassMergeBlocks::~FuzzerPassMergeBlocks() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassMergeBlocks::Apply() {
   // First we populate a sequence of transformations that we might consider
diff --git a/source/fuzz/fuzzer_pass_merge_blocks.h b/source/fuzz/fuzzer_pass_merge_blocks.h
index 1a6c2c2..46e257f 100644
--- a/source/fuzz/fuzzer_pass_merge_blocks.h
+++ b/source/fuzz/fuzzer_pass_merge_blocks.h
@@ -26,9 +26,8 @@
   FuzzerPassMergeBlocks(opt::IRContext* ir_context,
                         TransformationContext* transformation_context,
                         FuzzerContext* fuzzer_context,
-                        protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassMergeBlocks();
+                        protobufs::TransformationSequence* transformations,
+                        bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_merge_function_returns.cpp b/source/fuzz/fuzzer_pass_merge_function_returns.cpp
index fc9c74d..220f707 100644
--- a/source/fuzz/fuzzer_pass_merge_function_returns.cpp
+++ b/source/fuzz/fuzzer_pass_merge_function_returns.cpp
@@ -26,11 +26,10 @@
 FuzzerPassMergeFunctionReturns::FuzzerPassMergeFunctionReturns(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassMergeFunctionReturns::~FuzzerPassMergeFunctionReturns() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassMergeFunctionReturns::Apply() {
   // The pass might add new functions to the module (due to wrapping early
@@ -174,8 +173,9 @@
     }
 
     // Get the ids needed by the transformation.
-    uint32_t outer_header_id = GetFuzzerContext()->GetFreshId();
-    uint32_t outer_return_id = GetFuzzerContext()->GetFreshId();
+    const uint32_t outer_header_id = GetFuzzerContext()->GetFreshId();
+    const uint32_t unreachable_continue_id = GetFuzzerContext()->GetFreshId();
+    const uint32_t outer_return_id = GetFuzzerContext()->GetFreshId();
 
     bool function_is_void =
         GetIRContext()->get_type_mgr()->GetType(function->type_id())->AsVoid();
@@ -211,8 +211,8 @@
     // Apply the transformation if it is applicable (it could be inapplicable if
     // adding new predecessors to merge blocks breaks dominance rules).
     MaybeApplyTransformation(TransformationMergeFunctionReturns(
-        function->result_id(), outer_header_id, outer_return_id, return_val_id,
-        returnable_val_id, merge_blocks_info));
+        function->result_id(), outer_header_id, unreachable_continue_id,
+        outer_return_id, return_val_id, returnable_val_id, merge_blocks_info));
   }
 }
 
diff --git a/source/fuzz/fuzzer_pass_merge_function_returns.h b/source/fuzz/fuzzer_pass_merge_function_returns.h
index 3b5a668..a799b8b 100644
--- a/source/fuzz/fuzzer_pass_merge_function_returns.h
+++ b/source/fuzz/fuzzer_pass_merge_function_returns.h
@@ -31,9 +31,8 @@
   FuzzerPassMergeFunctionReturns(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassMergeFunctionReturns();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_mutate_pointers.cpp b/source/fuzz/fuzzer_pass_mutate_pointers.cpp
index 89f5f5c..bbe0540 100644
--- a/source/fuzz/fuzzer_pass_mutate_pointers.cpp
+++ b/source/fuzz/fuzzer_pass_mutate_pointers.cpp
@@ -24,11 +24,10 @@
 FuzzerPassMutatePointers::FuzzerPassMutatePointers(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassMutatePointers::~FuzzerPassMutatePointers() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassMutatePointers::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_mutate_pointers.h b/source/fuzz/fuzzer_pass_mutate_pointers.h
index f77523e..45c6a7c 100644
--- a/source/fuzz/fuzzer_pass_mutate_pointers.h
+++ b/source/fuzz/fuzzer_pass_mutate_pointers.h
@@ -26,9 +26,8 @@
   FuzzerPassMutatePointers(opt::IRContext* ir_context,
                            TransformationContext* transformation_context,
                            FuzzerContext* fuzzer_context,
-                           protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassMutatePointers() override;
+                           protobufs::TransformationSequence* transformations,
+                           bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
index d87662e..f60c1b4 100644
--- a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
@@ -30,11 +30,10 @@
 FuzzerPassObfuscateConstants::FuzzerPassObfuscateConstants(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassObfuscateConstants::~FuzzerPassObfuscateConstants() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassObfuscateConstants::ObfuscateBoolConstantViaConstantPair(
     uint32_t depth, const protobufs::IdUseDescriptor& bool_constant_use,
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.h b/source/fuzz/fuzzer_pass_obfuscate_constants.h
index d48b37f..30e64d2 100644
--- a/source/fuzz/fuzzer_pass_obfuscate_constants.h
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.h
@@ -30,9 +30,8 @@
   FuzzerPassObfuscateConstants(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassObfuscateConstants() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_outline_functions.cpp b/source/fuzz/fuzzer_pass_outline_functions.cpp
index 4210125..b90c12d 100644
--- a/source/fuzz/fuzzer_pass_outline_functions.cpp
+++ b/source/fuzz/fuzzer_pass_outline_functions.cpp
@@ -27,11 +27,10 @@
 FuzzerPassOutlineFunctions::FuzzerPassOutlineFunctions(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassOutlineFunctions::~FuzzerPassOutlineFunctions() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassOutlineFunctions::Apply() {
   std::vector<opt::Function*> original_functions;
diff --git a/source/fuzz/fuzzer_pass_outline_functions.h b/source/fuzz/fuzzer_pass_outline_functions.h
index 02022aa..d80dc4a 100644
--- a/source/fuzz/fuzzer_pass_outline_functions.h
+++ b/source/fuzz/fuzzer_pass_outline_functions.h
@@ -24,12 +24,11 @@
 // flow graph into their own functions.
 class FuzzerPassOutlineFunctions : public FuzzerPass {
  public:
-  FuzzerPassOutlineFunctions(
-      opt::IRContext* ir_context, TransformationContext* transformation_context,
-      FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassOutlineFunctions();
+  FuzzerPassOutlineFunctions(opt::IRContext* ir_context,
+                             TransformationContext* transformation_context,
+                             FuzzerContext* fuzzer_context,
+                             protobufs::TransformationSequence* transformations,
+                             bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.cpp b/source/fuzz/fuzzer_pass_permute_blocks.cpp
index 24c16fb..e55fae3 100644
--- a/source/fuzz/fuzzer_pass_permute_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_permute_blocks.cpp
@@ -22,11 +22,10 @@
 FuzzerPassPermuteBlocks::FuzzerPassPermuteBlocks(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassPermuteBlocks::~FuzzerPassPermuteBlocks() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassPermuteBlocks::Apply() {
   // For now we do something very simple: we randomly decide whether to move a
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.h b/source/fuzz/fuzzer_pass_permute_blocks.h
index e5a672c..39326c2 100644
--- a/source/fuzz/fuzzer_pass_permute_blocks.h
+++ b/source/fuzz/fuzzer_pass_permute_blocks.h
@@ -27,9 +27,8 @@
   FuzzerPassPermuteBlocks(opt::IRContext* ir_context,
                           TransformationContext* transformation_context,
                           FuzzerContext* fuzzer_context,
-                          protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassPermuteBlocks() override;
+                          protobufs::TransformationSequence* transformations,
+                          bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
index de6b03f..a8035b9 100644
--- a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
@@ -28,12 +28,10 @@
 FuzzerPassPermuteFunctionParameters::FuzzerPassPermuteFunctionParameters(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassPermuteFunctionParameters::~FuzzerPassPermuteFunctionParameters() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassPermuteFunctionParameters::Apply() {
   for (const auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.h b/source/fuzz/fuzzer_pass_permute_function_parameters.h
index bc1031c..c5b6ad4 100644
--- a/source/fuzz/fuzzer_pass_permute_function_parameters.h
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.h
@@ -32,9 +32,8 @@
   FuzzerPassPermuteFunctionParameters(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassPermuteFunctionParameters() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_permute_function_variables.cpp b/source/fuzz/fuzzer_pass_permute_function_variables.cpp
new file mode 100644
index 0000000..f8b9b45
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_variables.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2021 Mostafa Ashraf
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_function_variables.h"
+
+#include <algorithm>
+#include <numeric>
+#include <vector>
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_swap_function_variables.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermuteFunctionVariables::FuzzerPassPermuteFunctionVariables(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations, ignore_inapplicable_transformations) {
+}  // Here we call parent constructor.
+
+void FuzzerPassPermuteFunctionVariables::Apply() {
+  // Permuting OpVariable instructions in each function.
+  for (auto& function : *GetIRContext()->module()) {
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfPermutingFunctionVariables())) {
+      continue;
+    }
+
+    auto first_block = function.entry().get();
+
+    std::vector<opt::Instruction*> variables;
+    for (auto& instruction : *first_block) {
+      if (instruction.opcode() == SpvOpVariable) {
+        variables.push_back(&instruction);
+      }
+    }
+    if (variables.size() <= 1) {
+      continue;
+    }
+    do {
+      uint32_t instruction_1_index = GetFuzzerContext()->RandomIndex(variables);
+      uint32_t instruction_2_index = GetFuzzerContext()->RandomIndex(variables);
+
+      if (instruction_1_index != instruction_2_index) {
+        ApplyTransformation(TransformationSwapFunctionVariables(
+            variables[instruction_1_index]->result_id(),
+            variables[instruction_2_index]->result_id()));
+      }
+
+    } while (GetFuzzerContext()->ChoosePercentage(
+                 GetFuzzerContext()
+                     ->GetChanceOfSwappingAnotherPairOfFunctionVariables()) &&
+             variables.size() > 2);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_function_variables.h b/source/fuzz/fuzzer_pass_permute_function_variables.h
new file mode 100644
index 0000000..2ebc15f
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_variables.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2021 Mostafa Ashraf
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_FUNCTION_VARIABLES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_VARIABLES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This fuzzer pass permutes variables in functions in the module.
+class FuzzerPassPermuteFunctionVariables : public FuzzerPass {
+ public:
+  FuzzerPassPermuteFunctionVariables(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_VARIABLES_H_
diff --git a/source/fuzz/fuzzer_pass_permute_instructions.cpp b/source/fuzz/fuzzer_pass_permute_instructions.cpp
index 6867053..3ef76be 100644
--- a/source/fuzz/fuzzer_pass_permute_instructions.cpp
+++ b/source/fuzz/fuzzer_pass_permute_instructions.cpp
@@ -25,11 +25,10 @@
 FuzzerPassPermuteInstructions::FuzzerPassPermuteInstructions(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassPermuteInstructions::~FuzzerPassPermuteInstructions() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassPermuteInstructions::Apply() {
   // We are iterating over all instructions in all basic blocks.
diff --git a/source/fuzz/fuzzer_pass_permute_instructions.h b/source/fuzz/fuzzer_pass_permute_instructions.h
index e02ddfa..b7ccbcc 100644
--- a/source/fuzz/fuzzer_pass_permute_instructions.h
+++ b/source/fuzz/fuzzer_pass_permute_instructions.h
@@ -27,9 +27,8 @@
   FuzzerPassPermuteInstructions(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassPermuteInstructions() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_permute_phi_operands.cpp b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
index c379c53..5fac981 100644
--- a/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
+++ b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
@@ -28,11 +28,10 @@
 FuzzerPassPermutePhiOperands::FuzzerPassPermutePhiOperands(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassPermutePhiOperands::~FuzzerPassPermutePhiOperands() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassPermutePhiOperands::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_permute_phi_operands.h b/source/fuzz/fuzzer_pass_permute_phi_operands.h
index 974c2c1..30a9d4f 100644
--- a/source/fuzz/fuzzer_pass_permute_phi_operands.h
+++ b/source/fuzz/fuzzer_pass_permute_phi_operands.h
@@ -27,9 +27,8 @@
   FuzzerPassPermutePhiOperands(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassPermutePhiOperands() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp b/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
index 7a115ae..4c46dcd 100644
--- a/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
@@ -23,19 +23,16 @@
 FuzzerPassPropagateInstructionsDown::FuzzerPassPropagateInstructionsDown(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassPropagateInstructionsDown::~FuzzerPassPropagateInstructionsDown() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassPropagateInstructionsDown::Apply() {
   for (const auto& function : *GetIRContext()->module()) {
     std::vector<const opt::BasicBlock*> reachable_blocks;
     for (const auto& block : function) {
-      if (GetIRContext()->GetDominatorAnalysis(&function)->IsReachable(
-              &block)) {
+      if (GetIRContext()->IsReachable(block)) {
         reachable_blocks.push_back(&block);
       }
     }
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_down.h b/source/fuzz/fuzzer_pass_propagate_instructions_down.h
index 536bf00..18f0165 100644
--- a/source/fuzz/fuzzer_pass_propagate_instructions_down.h
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_down.h
@@ -26,9 +26,8 @@
   FuzzerPassPropagateInstructionsDown(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassPropagateInstructionsDown() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp b/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp
index 16ec680..5e45da8 100644
--- a/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp
@@ -25,12 +25,10 @@
 FuzzerPassPropagateInstructionsUp::FuzzerPassPropagateInstructionsUp(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassPropagateInstructionsUp::~FuzzerPassPropagateInstructionsUp() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassPropagateInstructionsUp::Apply() {
   for (const auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_up.h b/source/fuzz/fuzzer_pass_propagate_instructions_up.h
index d915b31..0cb8396 100644
--- a/source/fuzz/fuzzer_pass_propagate_instructions_up.h
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_up.h
@@ -27,9 +27,8 @@
   FuzzerPassPropagateInstructionsUp(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassPropagateInstructionsUp() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
index 8d9acaa..a6c07b4 100644
--- a/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
+++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.cpp
@@ -24,12 +24,10 @@
 FuzzerPassPushIdsThroughVariables::FuzzerPassPushIdsThroughVariables(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassPushIdsThroughVariables::~FuzzerPassPushIdsThroughVariables() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassPushIdsThroughVariables::Apply() {
   ForEachInstructionWithInstructionDescriptor(
@@ -50,7 +48,7 @@
 
         // The block containing the instruction we are going to insert before
         // must be reachable.
-        if (!fuzzerutil::BlockIsReachableInItsFunction(GetIRContext(), block)) {
+        if (!GetIRContext()->IsReachable(*block)) {
           return;
         }
 
@@ -72,6 +70,12 @@
         auto basic_type_ids_and_pointers =
             GetAvailableBasicTypesAndPointers(variable_storage_class);
         auto& basic_types = basic_type_ids_and_pointers.first;
+
+        // There must be at least some basic types.
+        if (basic_types.empty()) {
+          return;
+        }
+
         uint32_t basic_type_id =
             basic_types[GetFuzzerContext()->RandomIndex(basic_types)];
 
@@ -99,7 +103,7 @@
                            ->IdIsIrrelevant(instruction->result_id()) &&
                       !fuzzerutil::CanMakeSynonymOf(ir_context,
                                                     *GetTransformationContext(),
-                                                    instruction)) {
+                                                    *instruction)) {
                     return false;
                   }
 
diff --git a/source/fuzz/fuzzer_pass_push_ids_through_variables.h b/source/fuzz/fuzzer_pass_push_ids_through_variables.h
index 3ad5404..71b6949 100644
--- a/source/fuzz/fuzzer_pass_push_ids_through_variables.h
+++ b/source/fuzz/fuzzer_pass_push_ids_through_variables.h
@@ -28,9 +28,8 @@
   FuzzerPassPushIdsThroughVariables(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassPushIdsThroughVariables() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
index 139dc6e..467e613 100644
--- a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
+++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
@@ -29,12 +29,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
-    ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() {
   std::vector<opt::Instruction> instructions_for_transformation;
diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
index dd39e6b..268655f 100644
--- a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
+++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
@@ -29,9 +29,8 @@
   FuzzerPassReplaceAddsSubsMulsWithCarryingExtended(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp b/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp
index e6bebea..995657c 100644
--- a/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp
+++ b/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp
@@ -28,12 +28,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceBranchesFromDeadBlocksWithExits::
-    ~FuzzerPassReplaceBranchesFromDeadBlocksWithExits() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceBranchesFromDeadBlocksWithExits::Apply() {
   // OpKill can only be used as a terminator in a function that is guaranteed
diff --git a/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h b/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h
index 62164b3..cdbb66f 100644
--- a/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h
+++ b/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h
@@ -28,9 +28,8 @@
   FuzzerPassReplaceBranchesFromDeadBlocksWithExits(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceBranchesFromDeadBlocksWithExits() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
index 6847146..af1aace 100644
--- a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
+++ b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
@@ -26,12 +26,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceCopyMemoriesWithLoadsStores::
-    ~FuzzerPassReplaceCopyMemoriesWithLoadsStores() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceCopyMemoriesWithLoadsStores::Apply() {
   GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
diff --git a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h
index 9c24ac7..7d954ab 100644
--- a/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h
+++ b/source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h
@@ -28,9 +28,8 @@
   FuzzerPassReplaceCopyMemoriesWithLoadsStores(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceCopyMemoriesWithLoadsStores() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
index e372924..d0992a3 100644
--- a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
+++ b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
@@ -26,12 +26,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceCopyObjectsWithStoresLoads::
-    ~FuzzerPassReplaceCopyObjectsWithStoresLoads() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceCopyObjectsWithStoresLoads::Apply() {
   GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
diff --git a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h
index ae03a45..2ffc00b 100644
--- a/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h
+++ b/source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h
@@ -28,9 +28,8 @@
   FuzzerPassReplaceCopyObjectsWithStoresLoads(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceCopyObjectsWithStoresLoads() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
index 432addb..4d55ae8 100644
--- a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
+++ b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
@@ -27,11 +27,10 @@
 FuzzerPassReplaceIrrelevantIds::FuzzerPassReplaceIrrelevantIds(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceIrrelevantIds::~FuzzerPassReplaceIrrelevantIds() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceIrrelevantIds::Apply() {
   // Keep track of the irrelevant ids. This includes all the ids that are
diff --git a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h
index ab3f01d..80f8eb8 100644
--- a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h
+++ b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h
@@ -28,9 +28,8 @@
   FuzzerPassReplaceIrrelevantIds(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceIrrelevantIds();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp
index c3e6578..445dbfe 100644
--- a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp
+++ b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp
@@ -26,12 +26,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceLinearAlgebraInstructions::
-    ~FuzzerPassReplaceLinearAlgebraInstructions() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceLinearAlgebraInstructions::Apply() {
   // For each instruction, checks whether it is a linear algebra instruction. In
diff --git a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h
index 2c6126c..5734bf1 100644
--- a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h
+++ b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h
@@ -27,9 +27,8 @@
   FuzzerPassReplaceLinearAlgebraInstructions(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceLinearAlgebraInstructions();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
index 7690ac4..38ac048 100644
--- a/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
+++ b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
@@ -27,12 +27,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceLoadsStoresWithCopyMemories::
-    ~FuzzerPassReplaceLoadsStoresWithCopyMemories() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceLoadsStoresWithCopyMemories::Apply() {
   // We look for matching pairs of instructions OpLoad and
diff --git a/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h
index 67871db..f6209fc 100644
--- a/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h
+++ b/source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h
@@ -28,9 +28,8 @@
   FuzzerPassReplaceLoadsStoresWithCopyMemories(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceLoadsStoresWithCopyMemories() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
index 433cf74..ea90a7a 100644
--- a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
+++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
@@ -24,12 +24,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::
-    ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::Apply() {
   // Keep a vector of the transformations to apply.
diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
index 972c5f9..b01e242 100644
--- a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
+++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
@@ -27,9 +27,8 @@
   FuzzerPassReplaceOpPhiIdsFromDeadPredecessors(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp
index c3db0ef..72ed093 100644
--- a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp
+++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp
@@ -27,12 +27,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceOpSelectsWithConditionalBranches::
-    ~FuzzerPassReplaceOpSelectsWithConditionalBranches() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceOpSelectsWithConditionalBranches::Apply() {
   // Keep track of the instructions that we want to replace. We need to collect
diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h
index 04c6cc6..174962e 100644
--- a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h
+++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h
@@ -27,9 +27,8 @@
   FuzzerPassReplaceOpSelectsWithConditionalBranches(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceOpSelectsWithConditionalBranches() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp b/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp
index 6b3a63b..7fb7b0d 100644
--- a/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp
+++ b/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp
@@ -27,12 +27,10 @@
 FuzzerPassReplaceParameterWithGlobal::FuzzerPassReplaceParameterWithGlobal(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceParameterWithGlobal::~FuzzerPassReplaceParameterWithGlobal() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceParameterWithGlobal::Apply() {
   for (const auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_replace_parameter_with_global.h b/source/fuzz/fuzzer_pass_replace_parameter_with_global.h
index 25011bd..4eb5086 100644
--- a/source/fuzz/fuzzer_pass_replace_parameter_with_global.h
+++ b/source/fuzz/fuzzer_pass_replace_parameter_with_global.h
@@ -27,9 +27,8 @@
   FuzzerPassReplaceParameterWithGlobal(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceParameterWithGlobal() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp b/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp
index 0e0610f..f029316 100644
--- a/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp
+++ b/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp
@@ -27,12 +27,10 @@
 FuzzerPassReplaceParamsWithStruct::FuzzerPassReplaceParamsWithStruct(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassReplaceParamsWithStruct::~FuzzerPassReplaceParamsWithStruct() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassReplaceParamsWithStruct::Apply() {
   for (const auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_replace_params_with_struct.h b/source/fuzz/fuzzer_pass_replace_params_with_struct.h
index ed1aa6b..3af7367 100644
--- a/source/fuzz/fuzzer_pass_replace_params_with_struct.h
+++ b/source/fuzz/fuzzer_pass_replace_params_with_struct.h
@@ -27,9 +27,8 @@
   FuzzerPassReplaceParamsWithStruct(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassReplaceParamsWithStruct() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_split_blocks.cpp b/source/fuzz/fuzzer_pass_split_blocks.cpp
index 481cd96..40a4151 100644
--- a/source/fuzz/fuzzer_pass_split_blocks.cpp
+++ b/source/fuzz/fuzzer_pass_split_blocks.cpp
@@ -25,11 +25,10 @@
 FuzzerPassSplitBlocks::FuzzerPassSplitBlocks(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassSplitBlocks::~FuzzerPassSplitBlocks() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassSplitBlocks::Apply() {
   // Gather up pointers to all the blocks in the module.  We are then able to
diff --git a/source/fuzz/fuzzer_pass_split_blocks.h b/source/fuzz/fuzzer_pass_split_blocks.h
index 0ece48a..b1b9424 100644
--- a/source/fuzz/fuzzer_pass_split_blocks.h
+++ b/source/fuzz/fuzzer_pass_split_blocks.h
@@ -27,9 +27,8 @@
   FuzzerPassSplitBlocks(opt::IRContext* ir_context,
                         TransformationContext* transformation_context,
                         FuzzerContext* fuzzer_context,
-                        protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassSplitBlocks() override;
+                        protobufs::TransformationSequence* transformations,
+                        bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp b/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp
index 321e8ef..dce65f0 100644
--- a/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp
+++ b/source/fuzz/fuzzer_pass_swap_commutable_operands.cpp
@@ -24,11 +24,10 @@
 FuzzerPassSwapCommutableOperands::FuzzerPassSwapCommutableOperands(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassSwapCommutableOperands::~FuzzerPassSwapCommutableOperands() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassSwapCommutableOperands::Apply() {
   auto context = GetIRContext();
diff --git a/source/fuzz/fuzzer_pass_swap_commutable_operands.h b/source/fuzz/fuzzer_pass_swap_commutable_operands.h
index 74d937d..13d8fb6 100644
--- a/source/fuzz/fuzzer_pass_swap_commutable_operands.h
+++ b/source/fuzz/fuzzer_pass_swap_commutable_operands.h
@@ -28,9 +28,8 @@
   FuzzerPassSwapCommutableOperands(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassSwapCommutableOperands();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp
index 9433a61..f8bf111 100644
--- a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp
+++ b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp
@@ -27,12 +27,10 @@
         opt::IRContext* ir_context,
         TransformationContext* transformation_context,
         FuzzerContext* fuzzer_context,
-        protobufs::TransformationSequence* transformations)
+        protobufs::TransformationSequence* transformations,
+        bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassSwapBranchConditionalOperands::
-    ~FuzzerPassSwapBranchConditionalOperands() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassSwapBranchConditionalOperands::Apply() {
   ForEachInstructionWithInstructionDescriptor(
diff --git a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h
index f84f3ba..7f71f9b 100644
--- a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h
+++ b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h
@@ -27,9 +27,8 @@
   FuzzerPassSwapBranchConditionalOperands(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassSwapBranchConditionalOperands() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_swap_functions.cpp b/source/fuzz/fuzzer_pass_swap_functions.cpp
new file mode 100644
index 0000000..8eeec85
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_swap_functions.cpp
@@ -0,0 +1,53 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_swap_functions.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/transformation_swap_two_functions.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassSwapFunctions::FuzzerPassSwapFunctions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations, ignore_inapplicable_transformations) {}
+
+void FuzzerPassSwapFunctions::Apply() {
+  // Collect all function ids in a module.
+  std::vector<uint32_t> function_ids;
+  for (auto& function : *GetIRContext()->module()) {
+    function_ids.emplace_back(function.result_id());
+  }
+
+  // Iterate through every combination of id i & j where i!=j.
+  for (size_t i = 0; i < function_ids.size(); ++i) {
+    for (size_t j = i + 1; j < function_ids.size(); ++j) {
+      // Perform function swap randomly.
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfSwappingFunctions())) {
+        continue;
+      }
+      TransformationSwapTwoFunctions transformation(function_ids[i],
+                                                    function_ids[j]);
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_swap_functions.h b/source/fuzz/fuzzer_pass_swap_functions.h
new file mode 100644
index 0000000..7af527f
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_swap_functions.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_SWAP_FUNCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_SWAP_FUNCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly swap functions within a module.
+class FuzzerPassSwapFunctions : public FuzzerPass {
+ public:
+  FuzzerPassSwapFunctions(opt::IRContext* ir_context,
+                          TransformationContext* transformation_context,
+                          FuzzerContext* fuzzer_context,
+                          protobufs::TransformationSequence* transformations,
+                          bool ignore_inapplicable_transformations);
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_SWAP_FUNCTIONS_H_
diff --git a/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp
index 4f26cba..ac2b156 100644
--- a/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp
+++ b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.cpp
@@ -24,12 +24,10 @@
 FuzzerPassToggleAccessChainInstruction::FuzzerPassToggleAccessChainInstruction(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassToggleAccessChainInstruction::
-    ~FuzzerPassToggleAccessChainInstruction() = default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassToggleAccessChainInstruction::Apply() {
   auto context = GetIRContext();
diff --git a/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h
index d77c7cb..f0b6166 100644
--- a/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h
+++ b/source/fuzz/fuzzer_pass_toggle_access_chain_instruction.h
@@ -27,9 +27,8 @@
   FuzzerPassToggleAccessChainInstruction(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassToggleAccessChainInstruction();
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 };
diff --git a/source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp
index e6cdca4..3a3b12c 100644
--- a/source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp
+++ b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp
@@ -25,12 +25,10 @@
 FuzzerPassWrapRegionsInSelections::FuzzerPassWrapRegionsInSelections(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
     FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformations)
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                 transformations) {}
-
-FuzzerPassWrapRegionsInSelections::~FuzzerPassWrapRegionsInSelections() =
-    default;
+                 transformations, ignore_inapplicable_transformations) {}
 
 void FuzzerPassWrapRegionsInSelections::Apply() {
   for (auto& function : *GetIRContext()->module()) {
diff --git a/source/fuzz/fuzzer_pass_wrap_regions_in_selections.h b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.h
index eb28d20..fc3d7df 100644
--- a/source/fuzz/fuzzer_pass_wrap_regions_in_selections.h
+++ b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.h
@@ -27,9 +27,8 @@
   FuzzerPassWrapRegionsInSelections(
       opt::IRContext* ir_context, TransformationContext* transformation_context,
       FuzzerContext* fuzzer_context,
-      protobufs::TransformationSequence* transformations);
-
-  ~FuzzerPassWrapRegionsInSelections() override;
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
 
   void Apply() override;
 
diff --git a/source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp b/source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp
new file mode 100644
index 0000000..35adcfe
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp
@@ -0,0 +1,144 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_wrap_vector_synonym.h"
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_composite_construct.h"
+#include "source/fuzz/transformation_wrap_vector_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassWrapVectorSynonym::FuzzerPassWrapVectorSynonym(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations,
+    bool ignore_inapplicable_transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations, ignore_inapplicable_transformations) {}
+
+void FuzzerPassWrapVectorSynonym::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
+             opt::BasicBlock::iterator instruction_iterator,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+
+        // Randomly decide whether to wrap it to a vector operation.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfWrappingVectorSynonym())) {
+          return;
+        }
+
+        // The transformation is not applicable if the instruction has missing
+        // result id, type id, or is not supported type.
+        if (!TransformationWrapVectorSynonym::IsInstructionSupported(
+                GetIRContext(), *instruction_iterator)) {
+          return;
+        }
+
+        // It must be valid to insert an OpCompositeConstruct instruction
+        // before |instruction_iterator|.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                SpvOpCompositeConstruct, instruction_iterator)) {
+          return;
+        }
+
+        // Get the scalar operands from the original instruction.
+        opt::Instruction* operand1 = GetIRContext()->get_def_use_mgr()->GetDef(
+            instruction_iterator->GetSingleWordInOperand(0));
+        opt::Instruction* operand2 = GetIRContext()->get_def_use_mgr()->GetDef(
+            instruction_iterator->GetSingleWordInOperand(1));
+
+        // We need to be able to make a synonym of the scalar operation's result
+        // id, as well as the operand ids (for example, they cannot be
+        // irrelevant).
+        if (!fuzzerutil::CanMakeSynonymOf(GetIRContext(),
+                                          *GetTransformationContext(),
+                                          *instruction_iterator)) {
+          return;
+        }
+        if (!fuzzerutil::CanMakeSynonymOf(
+                GetIRContext(), *GetTransformationContext(), *operand1)) {
+          return;
+        }
+        if (!fuzzerutil::CanMakeSynonymOf(
+                GetIRContext(), *GetTransformationContext(), *operand2)) {
+          return;
+        }
+
+        // Get a random vector size from 2 to 4.
+        uint32_t vector_size = GetFuzzerContext()->GetWidthOfWrappingVector();
+
+        // Randomly choose a position that target ids should be placed at.
+        // The position is in range [0, n - 1], where n is the size of the
+        // vector.
+        uint32_t position =
+            GetFuzzerContext()->GetRandomIndexForWrappingVector(vector_size);
+
+        // Stores the ids of scalar constants.
+        std::vector<uint32_t> vec1_components;
+        std::vector<uint32_t> vec2_components;
+
+        // Populate components based on vector type and size.
+        for (uint32_t i = 0; i < vector_size; ++i) {
+          if (i == position) {
+            vec1_components.emplace_back(operand1->result_id());
+            vec2_components.emplace_back(operand2->result_id());
+          } else {
+            vec1_components.emplace_back(
+                FindOrCreateZeroConstant(operand1->type_id(), true));
+            vec2_components.emplace_back(
+                FindOrCreateZeroConstant(operand2->type_id(), true));
+          }
+        }
+
+        // Add two OpCompositeConstruct to the module with result id returned.
+        // The added vectors may have different types, for instance if the
+        // scalar instruction operates on integers with differing sign.
+
+        // Add the first OpCompositeConstruct that wraps the id of the first
+        // operand.
+        uint32_t result_id1 = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(TransformationCompositeConstruct(
+            FindOrCreateVectorType(operand1->type_id(), vector_size),
+            vec1_components, instruction_descriptor, result_id1));
+
+        // Add the second OpCompositeConstruct that wraps the id of the second
+        // operand.
+        uint32_t result_id2 = GetFuzzerContext()->GetFreshId();
+        ApplyTransformation(TransformationCompositeConstruct(
+            FindOrCreateVectorType(operand2->type_id(), vector_size),
+            vec2_components, instruction_descriptor, result_id2));
+
+        // The result of the vector instruction that
+        // TransformationWrapVectorSynonym will create should be a vector of the
+        // right size, with the scalar instruction's result type as its element
+        // type. This can be distinct from the types of the operands, if the
+        // scalar instruction adds two signed integers and stores the result in
+        // an unsigned id, for example. A transformation is applied to add the
+        // right type to the module.
+        FindOrCreateVectorType(instruction_iterator->type_id(), vector_size);
+
+        // Apply transformation to do vector operation and add synonym between
+        // the result vector id and the id of the original instruction.
+        ApplyTransformation(TransformationWrapVectorSynonym(
+            instruction_iterator->result_id(), result_id1, result_id2,
+            GetFuzzerContext()->GetFreshId(), position));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_wrap_vector_synonym.h b/source/fuzz/fuzzer_pass_wrap_vector_synonym.h
new file mode 100644
index 0000000..5145838
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_wrap_vector_synonym.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_WRAP_VECTOR_SYNONYM_H_
+#define SOURCE_FUZZ_FUZZER_PASS_WRAP_VECTOR_SYNONYM_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly wrap a scalar operation into a vector operation.
+class FuzzerPassWrapVectorSynonym : public FuzzerPass {
+ public:
+  FuzzerPassWrapVectorSynonym(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations,
+      bool ignore_inapplicable_transformations);
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_WRAP_VECTOR_SYNONYM_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 8c14db4..1d368a9 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -25,6 +25,25 @@
 namespace fuzzerutil {
 namespace {
 
+// A utility class that uses RAII to change and restore the terminator
+// instruction of the |block|.
+class ChangeTerminatorRAII {
+ public:
+  explicit ChangeTerminatorRAII(opt::BasicBlock* block,
+                                opt::Instruction new_terminator)
+      : block_(block), old_terminator_(std::move(*block->terminator())) {
+    *block_->terminator() = std::move(new_terminator);
+  }
+
+  ~ChangeTerminatorRAII() {
+    *block_->terminator() = std::move(old_terminator_);
+  }
+
+ private:
+  opt::BasicBlock* block_;
+  opt::Instruction old_terminator_;
+};
+
 uint32_t MaybeGetOpConstant(opt::IRContext* ir_context,
                             const TransformationContext& transformation_context,
                             const std::vector<uint32_t>& words,
@@ -47,6 +66,34 @@
     [](spv_message_level_t, const char*, const spv_position_t&,
        const char*) -> void {};
 
+bool BuildIRContext(spv_target_env target_env,
+                    const spvtools::MessageConsumer& message_consumer,
+                    const std::vector<uint32_t>& binary_in,
+                    spv_validator_options validator_options,
+                    std::unique_ptr<spvtools::opt::IRContext>* ir_context) {
+  SpirvTools tools(target_env);
+  tools.SetMessageConsumer(message_consumer);
+  if (!tools.IsValid()) {
+    message_consumer(SPV_MSG_ERROR, nullptr, {},
+                     "Failed to create SPIRV-Tools interface; stopping.");
+    return false;
+  }
+
+  // Initial binary should be valid.
+  if (!tools.Validate(binary_in.data(), binary_in.size(), validator_options)) {
+    message_consumer(SPV_MSG_ERROR, nullptr, {},
+                     "Initial binary is invalid; stopping.");
+    return false;
+  }
+
+  // Build the module from the input binary.
+  auto result = BuildModule(target_env, message_consumer, binary_in.data(),
+                            binary_in.size());
+  assert(result && "IRContext must be valid");
+  *ir_context = std::move(result);
+  return true;
+}
+
 bool IsFreshId(opt::IRContext* context, uint32_t id) {
   return !context->get_def_use_mgr()->GetDef(id);
 }
@@ -135,35 +182,46 @@
   return true;
 }
 
-void AddUnreachableEdgeAndUpdateOpPhis(
-    opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
-    uint32_t bool_id,
-    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");
+opt::Instruction CreateUnreachableEdgeInstruction(opt::IRContext* ir_context,
+                                                  uint32_t bb_from_id,
+                                                  uint32_t bb_to_id,
+                                                  uint32_t bool_id) {
+  const auto* bb_from = MaybeFindBlock(ir_context, bb_from_id);
+  assert(bb_from && "|bb_from_id| is invalid");
+  assert(MaybeFindBlock(ir_context, bb_to_id) && "|bb_to_id| is invalid");
   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.
-  auto condition_inst = context->get_def_use_mgr()->GetDef(bool_id);
+  auto condition_inst = ir_context->get_def_use_mgr()->GetDef(bool_id);
   assert(condition_inst &&
          (condition_inst->opcode() == SpvOpConstantTrue ||
           condition_inst->opcode() == SpvOpConstantFalse) &&
          "|bool_id| is invalid");
 
   auto condition_value = condition_inst->opcode() == SpvOpConstantTrue;
-
-  const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
-  auto successor = bb_from->terminator()->GetSingleWordInOperand(0);
+  auto successor_id = 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(
+  return opt::Instruction(
+      ir_context, SpvOpBranchConditional, 0, 0,
       {{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}}});
+       {SPV_OPERAND_TYPE_ID, {condition_value ? successor_id : bb_to_id}},
+       {SPV_OPERAND_TYPE_ID, {condition_value ? bb_to_id : successor_id}}});
+}
+
+void AddUnreachableEdgeAndUpdateOpPhis(
+    opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
+    uint32_t bool_id,
+    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");
+
+  const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to);
+  *bb_from->terminator() = CreateUnreachableEdgeInstruction(
+      context, bb_from->id(), bb_to->id(), bool_id);
 
   // Update OpPhi instructions in the target block if this branch adds a
   // previously non-existent edge from source to target.
@@ -194,11 +252,11 @@
     return false;
   }
 
-  // |block_id| must be reachable and be dominated by |loop_header|.
+  // |block| must be reachable and be dominated by |loop_header|.
   opt::DominatorAnalysis* dominator_analysis =
       context->GetDominatorAnalysis(loop_header->GetParent());
-  return dominator_analysis->IsReachable(block_id) &&
-         dominator_analysis->Dominates(loop_header_id, block_id);
+  return context->IsReachable(*block) &&
+         dominator_analysis->Dominates(loop_header, block);
 }
 
 bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id,
@@ -226,13 +284,6 @@
   return block->end();
 }
 
-bool BlockIsReachableInItsFunction(opt::IRContext* context,
-                                   opt::BasicBlock* bb) {
-  auto enclosing_function = bb->GetParent();
-  return context->GetDominatorAnalysis(enclosing_function)
-      ->Dominates(enclosing_function->entry().get(), bb);
-}
-
 bool CanInsertOpcodeBeforeInstruction(
     SpvOp opcode, const opt::BasicBlock::iterator& instruction_in_block) {
   if (instruction_in_block->PreviousNode() &&
@@ -254,34 +305,34 @@
 
 bool CanMakeSynonymOf(opt::IRContext* ir_context,
                       const TransformationContext& transformation_context,
-                      opt::Instruction* inst) {
-  if (inst->opcode() == SpvOpSampledImage) {
+                      const opt::Instruction& inst) {
+  if (inst.opcode() == SpvOpSampledImage) {
     // The SPIR-V data rules say that only very specific instructions may
     // may consume the result id of an OpSampledImage, and this excludes the
     // instructions that are used for making synonyms.
     return false;
   }
-  if (!inst->HasResultId()) {
+  if (!inst.HasResultId()) {
     // We can only make a synonym of an instruction that generates an id.
     return false;
   }
   if (transformation_context.GetFactManager()->IdIsIrrelevant(
-          inst->result_id())) {
+          inst.result_id())) {
     // An irrelevant id can't be a synonym of anything.
     return false;
   }
-  if (!inst->type_id()) {
+  if (!inst.type_id()) {
     // We can only make a synonym of an instruction that has a type.
     return false;
   }
-  auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst->type_id());
+  auto type_inst = ir_context->get_def_use_mgr()->GetDef(inst.type_id());
   if (type_inst->opcode() == SpvOpTypeVoid) {
     // We only make synonyms of instructions that define objects, and an object
     // cannot have void type.
     return false;
   }
   if (type_inst->opcode() == SpvOpTypePointer) {
-    switch (inst->opcode()) {
+    switch (inst.opcode()) {
       case SpvOpConstantNull:
       case SpvOpUndef:
         // We disallow making synonyms of null or undefined pointers.  This is
@@ -297,7 +348,7 @@
   // not decorated analogously, using the original object vs. its synonymous
   // form may not be equivalent.
   return ir_context->get_decoration_mgr()
-      ->GetDecorationsFor(inst->result_id(), true)
+      ->GetDecorationsFor(inst.result_id(), true)
       .empty();
 }
 
@@ -404,13 +455,37 @@
   }
 }
 
+SpvMemorySemanticsMask GetMemorySemanticsForStorageClass(
+    SpvStorageClass storage_class) {
+  switch (storage_class) {
+    case SpvStorageClassWorkgroup:
+      return SpvMemorySemanticsWorkgroupMemoryMask;
+
+    case SpvStorageClassStorageBuffer:
+    case SpvStorageClassPhysicalStorageBuffer:
+      return SpvMemorySemanticsUniformMemoryMask;
+
+    case SpvStorageClassCrossWorkgroup:
+      return SpvMemorySemanticsCrossWorkgroupMemoryMask;
+
+    case SpvStorageClassAtomicCounter:
+      return SpvMemorySemanticsAtomicCounterMemoryMask;
+
+    case SpvStorageClassImage:
+      return SpvMemorySemanticsImageMemoryMask;
+
+    default:
+      return SpvMemorySemanticsMaskNone;
+  }
+}
+
 bool IsValid(const opt::IRContext* context,
              spv_validator_options validator_options,
              MessageConsumer consumer) {
   std::vector<uint32_t> binary;
   context->module()->ToBinary(&binary, false);
   SpirvTools tools(context->grammar().target_env());
-  tools.SetMessageConsumer(consumer);
+  tools.SetMessageConsumer(std::move(consumer));
   return tools.Validate(binary.data(), binary.size(), validator_options);
 }
 
@@ -451,8 +526,12 @@
   // this is a useful aid to debugging.
   std::unordered_map<uint32_t, opt::Instruction*> unique_ids;
   bool found_duplicate = false;
-  ir_context->module()->ForEachInst([&consumer, &found_duplicate,
+  ir_context->module()->ForEachInst([&consumer, &found_duplicate, ir_context,
                                      &unique_ids](opt::Instruction* inst) {
+    (void)ir_context;  // Only used in an assertion; keep release-mode compilers
+                       // happy.
+    assert(inst->context() == ir_context &&
+           "Instruction has wrong IR context.");
     if (unique_ids.count(inst->unique_id()) != 0) {
       consumer(SPV_MSG_INFO, nullptr, {},
                "Two instructions have the same unique id (set a breakpoint to "
@@ -602,13 +681,12 @@
     // It is not OK for a definition to use itself.
     return false;
   }
-  auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
-  if (!dominator_analysis->IsReachable(
-          context->get_instr_block(use_instruction)) ||
-      !dominator_analysis->IsReachable(context->get_instr_block(id))) {
+  if (!context->IsReachable(*context->get_instr_block(use_instruction)) ||
+      !context->IsReachable(*context->get_instr_block(id))) {
     // Skip unreachable blocks.
     return false;
   }
+  auto dominator_analysis = context->GetDominatorAnalysis(enclosing_function);
   if (use_instruction->opcode() == SpvOpPhi) {
     // In the case where the use is an operand to OpPhi, it is actually the
     // *parent* block associated with the operand that must be dominated by
@@ -646,8 +724,8 @@
   }
   const auto* dominator_analysis =
       context->GetDominatorAnalysis(function_enclosing_instruction);
-  if (dominator_analysis->IsReachable(context->get_instr_block(instruction)) &&
-      dominator_analysis->IsReachable(context->get_instr_block(id)) &&
+  if (context->IsReachable(*context->get_instr_block(instruction)) &&
+      context->IsReachable(*context->get_instr_block(id)) &&
       dominator_analysis->Dominates(id_definition, instruction)) {
     // The id's definition dominates the instruction, and both the definition
     // and the instruction are in reachable blocks, thus the id is available at
@@ -657,8 +735,7 @@
   if (id_definition->opcode() == SpvOpVariable &&
       function_enclosing_instruction ==
           context->get_instr_block(id)->GetParent()) {
-    assert(!dominator_analysis->IsReachable(
-               context->get_instr_block(instruction)) &&
+    assert(!context->IsReachable(*context->get_instr_block(instruction)) &&
            "If the instruction were in a reachable block we should already "
            "have returned true.");
     // The id is a variable and it is in the same function as |instruction|.
@@ -738,23 +815,51 @@
   return absolute_index - inst.NumOperands() + inst.NumInOperands();
 }
 
-bool IsNullConstantSupported(const opt::analysis::Type& type) {
-  return type.AsBool() || type.AsInteger() || type.AsFloat() ||
-         type.AsMatrix() || type.AsVector() || type.AsArray() ||
-         type.AsStruct() || type.AsPointer() || type.AsEvent() ||
-         type.AsDeviceEvent() || type.AsReserveId() || type.AsQueue();
+bool IsNullConstantSupported(opt::IRContext* ir_context,
+                             const opt::Instruction& type_inst) {
+  switch (type_inst.opcode()) {
+    case SpvOpTypeArray:
+    case SpvOpTypeBool:
+    case SpvOpTypeDeviceEvent:
+    case SpvOpTypeEvent:
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt:
+    case SpvOpTypeMatrix:
+    case SpvOpTypeQueue:
+    case SpvOpTypeReserveId:
+    case SpvOpTypeVector:
+    case SpvOpTypeStruct:
+      return true;
+    case SpvOpTypePointer:
+      // Null pointers are allowed if the VariablePointers capability is
+      // enabled, or if the VariablePointersStorageBuffer capability is enabled
+      // and the pointer type has StorageBuffer as its storage class.
+      if (ir_context->get_feature_mgr()->HasCapability(
+              SpvCapabilityVariablePointers)) {
+        return true;
+      }
+      if (ir_context->get_feature_mgr()->HasCapability(
+              SpvCapabilityVariablePointersStorageBuffer)) {
+        return type_inst.GetSingleWordInOperand(0) ==
+               SpvStorageClassStorageBuffer;
+      }
+      return false;
+    default:
+      return false;
+  }
 }
 
 bool GlobalVariablesMustBeDeclaredInEntryPointInterfaces(
     const opt::IRContext* ir_context) {
-  // TODO(afd): We capture the universal environments for which this requirement
-  //  holds.  The check should be refined on demand for other target
-  //  environments.
+  // TODO(afd): We capture the environments for which this requirement holds.
+  //  The check should be refined on demand for other target environments.
   switch (ir_context->grammar().target_env()) {
     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_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1:
       return false;
     default:
       return true;
@@ -778,9 +883,10 @@
   }
 }
 
-void AddGlobalVariable(opt::IRContext* context, uint32_t result_id,
-                       uint32_t type_id, SpvStorageClass storage_class,
-                       uint32_t initializer_id) {
+opt::Instruction* AddGlobalVariable(opt::IRContext* context, uint32_t result_id,
+                                    uint32_t type_id,
+                                    SpvStorageClass storage_class,
+                                    uint32_t initializer_id) {
   // Check various preconditions.
   assert(result_id != 0 && "Result id can't be 0");
 
@@ -815,16 +921,20 @@
     operands.push_back({SPV_OPERAND_TYPE_ID, {initializer_id}});
   }
 
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context, SpvOpVariable, type_id, result_id, std::move(operands)));
+  auto new_instruction = MakeUnique<opt::Instruction>(
+      context, SpvOpVariable, type_id, result_id, std::move(operands));
+  auto result = new_instruction.get();
+  context->module()->AddGlobalValue(std::move(new_instruction));
 
   AddVariableIdToEntryPointInterfaces(context, result_id);
   UpdateModuleIdBound(context, result_id);
+
+  return result;
 }
 
-void AddLocalVariable(opt::IRContext* context, uint32_t result_id,
-                      uint32_t type_id, uint32_t function_id,
-                      uint32_t initializer_id) {
+opt::Instruction* AddLocalVariable(opt::IRContext* context, uint32_t result_id,
+                                   uint32_t type_id, uint32_t function_id,
+                                   uint32_t initializer_id) {
   // Check various preconditions.
   assert(result_id != 0 && "Result id can't be 0");
 
@@ -845,13 +955,17 @@
   auto* function = FindFunction(context, function_id);
   assert(function && "Function id is invalid");
 
-  function->begin()->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+  auto new_instruction = MakeUnique<opt::Instruction>(
       context, SpvOpVariable, type_id, result_id,
       opt::Instruction::OperandList{
           {SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}},
-          {SPV_OPERAND_TYPE_ID, {initializer_id}}}));
+          {SPV_OPERAND_TYPE_ID, {initializer_id}}});
+  auto result = new_instruction.get();
+  function->begin()->begin()->InsertBefore(std::move(new_instruction));
 
   UpdateModuleIdBound(context, result_id);
+
+  return result;
 }
 
 bool HasDuplicates(const std::vector<uint32_t>& arr) {
@@ -1355,74 +1469,6 @@
   return 0;
 }
 
-void AddIntegerType(opt::IRContext* ir_context, uint32_t result_id,
-                    uint32_t width, bool is_signed) {
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeInt, 0, result_id,
-      opt::Instruction::OperandList{
-          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {width}},
-          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {is_signed ? 1u : 0u}}}));
-
-  UpdateModuleIdBound(ir_context, result_id);
-}
-
-void AddFloatType(opt::IRContext* ir_context, uint32_t result_id,
-                  uint32_t width) {
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeFloat, 0, result_id,
-      opt::Instruction::OperandList{
-          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {width}}}));
-
-  UpdateModuleIdBound(ir_context, result_id);
-}
-
-void AddVectorType(opt::IRContext* ir_context, uint32_t result_id,
-                   uint32_t component_type_id, uint32_t element_count) {
-  const auto* component_type =
-      ir_context->get_type_mgr()->GetType(component_type_id);
-  (void)component_type;  // Make compiler happy in release mode.
-  assert(component_type &&
-         (component_type->AsInteger() || component_type->AsFloat() ||
-          component_type->AsBool()) &&
-         "|component_type_id| is invalid");
-  assert(element_count >= 2 && element_count <= 4 &&
-         "Precondition: component count must be in range [2, 4].");
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeVector, 0, result_id,
-      opt::Instruction::OperandList{
-          {SPV_OPERAND_TYPE_ID, {component_type_id}},
-          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {element_count}}}));
-
-  UpdateModuleIdBound(ir_context, result_id);
-}
-
-void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
-                   const std::vector<uint32_t>& component_type_ids) {
-  opt::Instruction::OperandList operands;
-  operands.reserve(component_type_ids.size());
-
-  for (auto type_id : component_type_ids) {
-    const auto* type = ir_context->get_type_mgr()->GetType(type_id);
-    (void)type;  // Make compiler happy in release mode.
-    assert(type && !type->AsFunction() && "Component's type id is invalid");
-
-    if (type->AsStruct()) {
-      // From the spec for the BuiltIn decoration:
-      // - When applied to a structure-type member, that structure type cannot
-      //   be contained as a member of another structure type.
-      assert(!MembersHaveBuiltInDecoration(ir_context, type_id) &&
-             "A member struct has BuiltIn members");
-    }
-
-    operands.push_back({SPV_OPERAND_TYPE_ID, {type_id}});
-  }
-
-  ir_context->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeStruct, 0, result_id, std::move(operands)));
-
-  UpdateModuleIdBound(ir_context, result_id);
-}
-
 std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width,
                                  bool is_signed) {
   assert(width <= 64 && "The bit width should not be more than 64 bits");
@@ -1625,6 +1671,44 @@
     return false;
   }
 
+  if (ir_context->get_feature_mgr()->HasCapability(SpvCapabilityShader)) {
+    // With the Shader capability, memory scope and memory semantics operands
+    // are required to be constants, so they cannot be replaced arbitrarily.
+    switch (use_instruction->opcode()) {
+      case SpvOpAtomicLoad:
+      case SpvOpAtomicStore:
+      case SpvOpAtomicExchange:
+      case SpvOpAtomicIIncrement:
+      case SpvOpAtomicIDecrement:
+      case SpvOpAtomicIAdd:
+      case SpvOpAtomicISub:
+      case SpvOpAtomicSMin:
+      case SpvOpAtomicUMin:
+      case SpvOpAtomicSMax:
+      case SpvOpAtomicUMax:
+      case SpvOpAtomicAnd:
+      case SpvOpAtomicOr:
+      case SpvOpAtomicXor:
+        if (use_in_operand_index == 1 || use_in_operand_index == 2) {
+          return false;
+        }
+        break;
+      case SpvOpAtomicCompareExchange:
+        if (use_in_operand_index == 1 || use_in_operand_index == 2 ||
+            use_in_operand_index == 3) {
+          return false;
+        }
+        break;
+      case SpvOpAtomicCompareExchangeWeak:
+      case SpvOpAtomicFlagTestAndSet:
+      case SpvOpAtomicFlagClear:
+      case SpvOpAtomicFAddEXT:
+        assert(false && "Not allowed with the Shader capability.");
+      default:
+        break;
+    }
+  }
+
   return true;
 }
 
@@ -1827,6 +1911,200 @@
   return result;
 }
 
+bool NewTerminatorPreservesDominationRules(opt::IRContext* ir_context,
+                                           uint32_t block_id,
+                                           opt::Instruction new_terminator) {
+  auto* mutated_block = MaybeFindBlock(ir_context, block_id);
+  assert(mutated_block && "|block_id| is invalid");
+
+  ChangeTerminatorRAII change_terminator_raii(mutated_block,
+                                              std::move(new_terminator));
+  opt::DominatorAnalysis dominator_analysis;
+  dominator_analysis.InitializeTree(*ir_context->cfg(),
+                                    mutated_block->GetParent());
+
+  // Check that each dominator appears before each dominated block.
+  std::unordered_map<uint32_t, size_t> positions;
+  for (const auto& block : *mutated_block->GetParent()) {
+    positions[block.id()] = positions.size();
+  }
+
+  std::queue<uint32_t> q({mutated_block->GetParent()->begin()->id()});
+  std::unordered_set<uint32_t> visited;
+  while (!q.empty()) {
+    auto block = q.front();
+    q.pop();
+    visited.insert(block);
+
+    auto success = ir_context->cfg()->block(block)->WhileEachSuccessorLabel(
+        [&positions, &visited, &dominator_analysis, block, &q](uint32_t id) {
+          if (id == block) {
+            // Handle the case when loop header and continue target are the same
+            // block.
+            return true;
+          }
+
+          if (dominator_analysis.Dominates(block, id) &&
+              positions[block] > positions[id]) {
+            // |block| dominates |id| but appears after |id| - violates
+            // domination rules.
+            return false;
+          }
+
+          if (!visited.count(id)) {
+            q.push(id);
+          }
+
+          return true;
+        });
+
+    if (!success) {
+      return false;
+    }
+  }
+
+  // For each instruction in the |block->GetParent()| function check whether
+  // all its dependencies satisfy domination rules (i.e. all id operands
+  // dominate that instruction).
+  for (const auto& block : *mutated_block->GetParent()) {
+    if (!ir_context->IsReachable(block)) {
+      // If some block is not reachable then we don't need to worry about the
+      // preservation of domination rules for its instructions.
+      continue;
+    }
+
+    for (const auto& inst : block) {
+      for (uint32_t i = 0; i < inst.NumInOperands();
+           i += inst.opcode() == SpvOpPhi ? 2 : 1) {
+        const auto& operand = inst.GetInOperand(i);
+        if (!spvIsInIdType(operand.type)) {
+          continue;
+        }
+
+        if (MaybeFindBlock(ir_context, operand.words[0])) {
+          // Ignore operands that refer to OpLabel instructions.
+          continue;
+        }
+
+        const auto* dependency_block =
+            ir_context->get_instr_block(operand.words[0]);
+        if (!dependency_block) {
+          // A global instruction always dominates all instructions in any
+          // function.
+          continue;
+        }
+
+        auto domination_target_id = inst.opcode() == SpvOpPhi
+                                        ? inst.GetSingleWordInOperand(i + 1)
+                                        : block.id();
+
+        if (!dominator_analysis.Dominates(dependency_block->id(),
+                                          domination_target_id)) {
+          return false;
+        }
+      }
+    }
+  }
+
+  return true;
+}
+
+opt::Module::iterator GetFunctionIterator(opt::IRContext* ir_context,
+                                          uint32_t function_id) {
+  return std::find_if(ir_context->module()->begin(),
+                      ir_context->module()->end(),
+                      [function_id](const opt::Function& f) {
+                        return f.result_id() == function_id;
+                      });
+}
+
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3582): Add all
+//  opcodes that are agnostic to signedness of operands to function.
+//  This is not exhaustive yet.
+bool IsAgnosticToSignednessOfOperand(SpvOp opcode,
+                                     uint32_t use_in_operand_index) {
+  switch (opcode) {
+    case SpvOpSNegate:
+    case SpvOpNot:
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+    case SpvOpSDiv:
+    case SpvOpSRem:
+    case SpvOpSMod:
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    case SpvOpULessThan:
+    case SpvOpSLessThan:
+    case SpvOpUGreaterThan:
+    case SpvOpSGreaterThan:
+    case SpvOpULessThanEqual:
+    case SpvOpSLessThanEqual:
+    case SpvOpUGreaterThanEqual:
+    case SpvOpSGreaterThanEqual:
+      return true;
+
+    case SpvOpAtomicStore:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFAddEXT:  // Capability AtomicFloat32AddEXT,
+      // AtomicFloat64AddEXT.
+      assert(use_in_operand_index != 0 &&
+             "Signedness check should not occur on a pointer operand.");
+      return use_in_operand_index == 1 || use_in_operand_index == 2;
+
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:  // Capability Kernel.
+      assert(use_in_operand_index != 0 &&
+             "Signedness check should not occur on a pointer operand.");
+      return use_in_operand_index >= 1 && use_in_operand_index <= 3;
+
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicFlagTestAndSet:  // Capability Kernel.
+    case SpvOpAtomicFlagClear:       // Capability Kernel.
+      assert(use_in_operand_index != 0 &&
+             "Signedness check should not occur on a pointer operand.");
+      return use_in_operand_index >= 1;
+
+    case SpvOpAccessChain:
+      // The signedness of indices does not matter.
+      return use_in_operand_index > 0;
+
+    default:
+      // Conservatively assume that the id cannot be swapped in other
+      // instructions.
+      return false;
+  }
+}
+
+bool TypesAreCompatible(opt::IRContext* ir_context, SpvOp opcode,
+                        uint32_t use_in_operand_index, uint32_t type_id_1,
+                        uint32_t type_id_2) {
+  assert(ir_context->get_type_mgr()->GetType(type_id_1) &&
+         ir_context->get_type_mgr()->GetType(type_id_2) &&
+         "Type ids are invalid");
+
+  return type_id_1 == type_id_2 ||
+         (IsAgnosticToSignednessOfOperand(opcode, use_in_operand_index) &&
+          fuzzerutil::TypesAreEqualUpToSign(ir_context, type_id_1, type_id_2));
+}
+
 }  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 4e6ec36..54aa14a 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -24,6 +24,7 @@
 #include "source/opt/basic_block.h"
 #include "source/opt/instruction.h"
 #include "source/opt/ir_context.h"
+#include "source/opt/module.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -38,6 +39,15 @@
 // Function type that produces a SPIR-V module.
 using ModuleSupplier = std::function<std::unique_ptr<opt::IRContext>()>;
 
+// Builds a new opt::IRContext object. Returns true if successful and changes
+// the |ir_context| parameter. Otherwise (if any errors occur), returns false
+// and |ir_context| remains unchanged.
+bool BuildIRContext(spv_target_env target_env,
+                    const spvtools::MessageConsumer& message_consumer,
+                    const std::vector<uint32_t>& binary_in,
+                    spv_validator_options validator_options,
+                    std::unique_ptr<spvtools::opt::IRContext>* ir_context);
+
 // Returns true if and only if the module does not define the given id.
 bool IsFreshId(opt::IRContext* context, uint32_t id);
 
@@ -59,6 +69,16 @@
     opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to,
     const google::protobuf::RepeatedField<google::protobuf::uint32>& phi_ids);
 
+// Returns an OpBranchConditional instruction that will create an unreachable
+// branch from |bb_from_id| to |bb_to_id|. |bool_id| must be a result id of
+// either OpConstantTrue or OpConstantFalse. Based on the opcode of |bool_id|,
+// operands of the returned instruction will be positioned in a way that the
+// branch from |bb_from_id| to |bb_to_id| is always unreachable.
+opt::Instruction CreateUnreachableEdgeInstruction(opt::IRContext* ir_context,
+                                                  uint32_t bb_from_id,
+                                                  uint32_t bb_to_id,
+                                                  uint32_t bool_id);
+
 // Requires that |bool_id| is a valid result id of either OpConstantTrue or
 // OpConstantFalse, that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids)
 // holds, and that bb_from ends with "OpBranch %some_block".  Turns OpBranch
@@ -88,11 +108,6 @@
 opt::BasicBlock::iterator GetIteratorForInstruction(
     opt::BasicBlock* block, const opt::Instruction* inst);
 
-// Returns true if and only if there is a path to |bb| from the entry block of
-// the function that contains |bb|.
-bool BlockIsReachableInItsFunction(opt::IRContext* context,
-                                   opt::BasicBlock* bb);
-
 // Determines whether it is OK to insert an instruction with opcode |opcode|
 // before |instruction_in_block|.
 bool CanInsertOpcodeBeforeInstruction(
@@ -103,7 +118,7 @@
 // does not participate in IdIsIrrelevant fact.
 bool CanMakeSynonymOf(opt::IRContext* ir_context,
                       const TransformationContext& transformation_context,
-                      opt::Instruction* inst);
+                      const opt::Instruction& inst);
 
 // Determines whether the given type is a composite; that is: an array, matrix,
 // struct or vector.
@@ -154,6 +169,10 @@
 uint32_t GetBoundForCompositeIndex(const opt::Instruction& composite_type_inst,
                                    opt::IRContext* ir_context);
 
+// Returns memory semantics mask for specific storage class.
+SpvMemorySemanticsMask GetMemorySemanticsForStorageClass(
+    SpvStorageClass storage_class);
+
 // Returns true if and only if |context| is valid, according to the validator
 // instantiated with |validator_options|.  |consumer| is used for error
 // reporting.
@@ -258,8 +277,10 @@
                                         uint32_t absolute_index);
 
 // Returns true if and only if |type| is one of the types for which it is legal
-// to have an OpConstantNull value.
-bool IsNullConstantSupported(const opt::analysis::Type& type);
+// to have an OpConstantNull value. This may depend on the capabilities declared
+// in |context|.
+bool IsNullConstantSupported(opt::IRContext* context,
+                             const opt::Instruction& type);
 
 // Returns true if and only if the SPIR-V version being used requires that
 // global variables accessed in the static call graph of an entry point need
@@ -284,9 +305,12 @@
 // - |initializer_id| must be 0 if |storage_class| is Workgroup, and otherwise
 //   may either be 0 or the id of a constant whose type is the pointee type of
 //   |type_id|.
-void AddGlobalVariable(opt::IRContext* context, uint32_t result_id,
-                       uint32_t type_id, SpvStorageClass storage_class,
-                       uint32_t initializer_id);
+//
+// Returns a pointer to the new global variable instruction.
+opt::Instruction* AddGlobalVariable(opt::IRContext* context, uint32_t result_id,
+                                    uint32_t type_id,
+                                    SpvStorageClass storage_class,
+                                    uint32_t initializer_id);
 
 // Adds an instruction to the start of |function_id|, of the form:
 //   |result_id| = OpVariable |type_id| Function |initializer_id|.
@@ -296,9 +320,11 @@
 // - |initializer_id| must be the id of a constant with the same type as the
 //   pointer's pointee type.
 // - |function_id| must be the id of a function.
-void AddLocalVariable(opt::IRContext* context, uint32_t result_id,
-                      uint32_t type_id, uint32_t function_id,
-                      uint32_t initializer_id);
+//
+// Returns a pointer to the new local variable instruction.
+opt::Instruction* AddLocalVariable(opt::IRContext* context, uint32_t result_id,
+                                   uint32_t type_id, uint32_t function_id,
+                                   uint32_t initializer_id);
 
 // Returns true if the vector |arr| has duplicates.
 bool HasDuplicates(const std::vector<uint32_t>& arr);
@@ -476,30 +502,6 @@
     const TransformationContext& transformation_context, bool value,
     bool is_irrelevant);
 
-// Creates a new OpTypeInt instruction in the module. Updates module's id bound
-// to accommodate for |result_id|.
-void AddIntegerType(opt::IRContext* ir_context, uint32_t result_id,
-                    uint32_t width, bool is_signed);
-
-// Creates a new OpTypeFloat instruction in the module. Updates module's id
-// bound to accommodate for |result_id|.
-void AddFloatType(opt::IRContext* ir_context, uint32_t result_id,
-                  uint32_t width);
-
-// Creates a new OpTypeVector instruction in the module. |component_type_id|
-// must be a valid result id of an OpTypeInt, OpTypeFloat or OpTypeBool
-// instruction in the module. |element_count| must be in the range [2, 4].
-// Updates module's id bound to accommodate for |result_id|.
-void AddVectorType(opt::IRContext* ir_context, uint32_t result_id,
-                   uint32_t component_type_id, uint32_t element_count);
-
-// Creates a new OpTypeStruct instruction in the module. Updates module's id
-// bound to accommodate for |result_id|. |component_type_ids| may not contain
-// a result id of an OpTypeFunction. if |component_type_ids| contains a result
-// of an OpTypeStruct instruction, that struct may not have BuiltIn members.
-void AddStructType(opt::IRContext* ir_context, uint32_t result_id,
-                   const std::vector<uint32_t>& component_type_ids);
-
 // Returns a vector of words representing the integer |value|, only considering
 // the last |width| bits. The last |width| bits are sign-extended if the value
 // is signed, zero-extended if it is unsigned.
@@ -588,6 +590,35 @@
 std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
                                             uint32_t function_id);
 
+// Returns true if changing terminator instruction to |new_terminator| in the
+// basic block with id |block_id| preserves domination rules and valid block
+// order (i.e. dominator must always appear before dominated in the CFG).
+// Returns false otherwise.
+bool NewTerminatorPreservesDominationRules(opt::IRContext* ir_context,
+                                           uint32_t block_id,
+                                           opt::Instruction new_terminator);
+
+// Return the iterator that points to the function with the corresponding
+// function id. If the function is not found, return the pointer pointing to
+// module()->end().
+opt::Module::iterator GetFunctionIterator(opt::IRContext* ir_context,
+                                          uint32_t function_id);
+
+// Returns true if the instruction with opcode |opcode| does not change its
+// behaviour depending on the signedness of the operand at
+// |use_in_operand_index|.
+// Assumes that the operand must be the id of an integer scalar or vector.
+bool IsAgnosticToSignednessOfOperand(SpvOp opcode,
+                                     uint32_t use_in_operand_index);
+
+// Returns true if |type_id_1| and |type_id_2| represent compatible types
+// given the context of the instruction with |opcode| (i.e. we can replace
+// an operand of |opcode| of the first type with an id of the second type
+// and vice-versa).
+bool TypesAreCompatible(opt::IRContext* ir_context, SpvOp opcode,
+                        uint32_t use_in_operand_index, uint32_t type_id_1,
+                        uint32_t type_id_2);
+
 }  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/instruction_descriptor.cpp b/source/fuzz/instruction_descriptor.cpp
index c0cc5e5..fb1ff76 100644
--- a/source/fuzz/instruction_descriptor.cpp
+++ b/source/fuzz/instruction_descriptor.cpp
@@ -20,37 +20,32 @@
 opt::Instruction* FindInstruction(
     const protobufs::InstructionDescriptor& instruction_descriptor,
     spvtools::opt::IRContext* context) {
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
-      bool found_base =
-          block.id() == instruction_descriptor.base_instruction_result_id();
-      uint32_t num_ignored = 0;
-      for (auto& instruction : block) {
-        if (instruction.HasResultId() &&
-            instruction.result_id() ==
-                instruction_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() ==
-                instruction_descriptor.target_instruction_opcode()) {
-          if (num_ignored == instruction_descriptor.num_opcodes_to_ignore()) {
-            return &instruction;
-          }
-          num_ignored++;
-        }
+  auto block = context->get_instr_block(
+      instruction_descriptor.base_instruction_result_id());
+  if (block == nullptr) {
+    return nullptr;
+  }
+  bool found_base =
+      block->id() == instruction_descriptor.base_instruction_result_id();
+  uint32_t num_ignored = 0;
+  for (auto& instruction : *block) {
+    if (instruction.HasResultId() &&
+        instruction.result_id() ==
+            instruction_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() ==
+                          instruction_descriptor.target_instruction_opcode()) {
+      if (num_ignored == instruction_descriptor.num_opcodes_to_ignore()) {
+        return &instruction;
       }
-      if (found_base) {
-        // We found the base instruction, but did not find the target
-        // instruction in the same block.
-        return nullptr;
-      }
+      num_ignored++;
     }
   }
   return nullptr;
diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h
index 80ac087..da61fda 100644
--- a/source/fuzz/pass_management/repeated_pass_instances.h
+++ b/source/fuzz/pass_management/repeated_pass_instances.h
@@ -73,6 +73,7 @@
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
 #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h"
 #include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.h"
+#include "source/fuzz/fuzzer_pass_wrap_vector_synonym.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -168,6 +169,7 @@
   REPEATED_PASS_INSTANCE(SplitBlocks);
   REPEATED_PASS_INSTANCE(SwapBranchConditionalOperands);
   REPEATED_PASS_INSTANCE(WrapRegionsInSelections);
+  REPEATED_PASS_INSTANCE(WrapVectorSynonym);
 #undef REPEATED_PASS_INSTANCE
 
  public:
diff --git a/source/fuzz/pass_management/repeated_pass_manager.cpp b/source/fuzz/pass_management/repeated_pass_manager.cpp
index 032f264..ec44373 100644
--- a/source/fuzz/pass_management/repeated_pass_manager.cpp
+++ b/source/fuzz/pass_management/repeated_pass_manager.cpp
@@ -14,6 +14,10 @@
 
 #include "source/fuzz/pass_management/repeated_pass_manager.h"
 
+#include "source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h"
+#include "source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h"
+#include "source/fuzz/pass_management/repeated_pass_manager_simple.h"
+
 namespace spvtools {
 namespace fuzz {
 
@@ -23,5 +27,25 @@
 
 RepeatedPassManager::~RepeatedPassManager() = default;
 
+std::unique_ptr<RepeatedPassManager> RepeatedPassManager::Create(
+    RepeatedPassStrategy strategy, FuzzerContext* fuzzer_context,
+    RepeatedPassInstances* pass_instances,
+    RepeatedPassRecommender* pass_recommender) {
+  switch (strategy) {
+    case RepeatedPassStrategy::kSimple:
+      return MakeUnique<RepeatedPassManagerSimple>(fuzzer_context,
+                                                   pass_instances);
+    case RepeatedPassStrategy::kLoopedWithRecommendations:
+      return MakeUnique<RepeatedPassManagerLoopedWithRecommendations>(
+          fuzzer_context, pass_instances, pass_recommender);
+    case RepeatedPassStrategy::kRandomWithRecommendations:
+      return MakeUnique<RepeatedPassManagerRandomWithRecommendations>(
+          fuzzer_context, pass_instances, pass_recommender);
+  }
+
+  assert(false && "Unreachable");
+  return nullptr;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_manager.h b/source/fuzz/pass_management/repeated_pass_manager.h
index 1c23179..1e6ae3e 100644
--- a/source/fuzz/pass_management/repeated_pass_manager.h
+++ b/source/fuzz/pass_management/repeated_pass_manager.h
@@ -18,11 +18,21 @@
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_pass.h"
 #include "source/fuzz/pass_management/repeated_pass_instances.h"
+#include "source/fuzz/pass_management/repeated_pass_recommender.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 
 namespace spvtools {
 namespace fuzz {
 
+// Each field of this enum corresponds to an available repeated pass
+// strategy, and is used to decide which kind of RepeatedPassManager object
+// to create.
+enum class RepeatedPassStrategy {
+  kSimple,
+  kRandomWithRecommendations,
+  kLoopedWithRecommendations
+};
+
 // An interface to encapsulate the manner in which the sequence of repeated
 // passes that are applied during fuzzing is chosen.  An implementation of this
 // interface could, for example, keep track of the history of passes that have
@@ -40,6 +50,12 @@
   virtual FuzzerPass* ChoosePass(
       const protobufs::TransformationSequence& applied_transformations) = 0;
 
+  // Creates a corresponding RepeatedPassManager based on the |strategy|.
+  static std::unique_ptr<RepeatedPassManager> Create(
+      RepeatedPassStrategy strategy, FuzzerContext* fuzzer_context,
+      RepeatedPassInstances* pass_instances,
+      RepeatedPassRecommender* pass_recommender);
+
  protected:
   FuzzerContext* GetFuzzerContext() { return fuzzer_context_; }
 
diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
index a933848..6c61c0d 100644
--- a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
+++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
@@ -351,6 +351,12 @@
          pass_instances_->GetReplaceIrrelevantIds(),
          pass_instances_->GetFlattenConditionalBranches()});
   }
+  if (&pass == pass_instances_->GetWrapVectorSynonym()) {
+    // This transformation introduces synonym facts and irrelevant ids.
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms(),
+                                  pass_instances_->GetReplaceIrrelevantIds()});
+  }
+
   assert(false && "Unreachable: every fuzzer pass should be dealt with.");
   return {};
 }
diff --git a/source/fuzz/protobufs/spirvfuzz_protobufs.h b/source/fuzz/protobufs/spirvfuzz_protobufs.h
index eb8cb14..46c2188 100644
--- a/source/fuzz/protobufs/spirvfuzz_protobufs.h
+++ b/source/fuzz/protobufs/spirvfuzz_protobufs.h
@@ -23,8 +23,11 @@
 
 #if defined(__clang__)
 #pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-parameter"
+#pragma clang diagnostic ignored "-Wunknown-warning-option"  // Must come first
+#pragma clang diagnostic ignored "-Wreserved-identifier"
 #pragma clang diagnostic ignored "-Wshadow"
+#pragma clang diagnostic ignored "-Wsuggest-destructor-override"
+#pragma clang diagnostic ignored "-Wunused-parameter"
 #elif defined(__GNUC__)
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 770a2dd..cba9397 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -557,6 +557,9 @@
     TransformationWrapEarlyTerminatorInFunction wrap_early_terminator_in_function = 83;
     TransformationMergeFunctionReturns merge_function_returns = 84;
     TransformationExpandVectorReduction expand_vector_reduction = 85;
+    TransformationSwapFunctionVariables swap_function_variables = 86;
+    TransformationSwapTwoFunctions swap_two_functions = 87;
+    TransformationWrapVectorSynonym wrap_vector_synonym = 88;
     // Add additional option using the next available number.
   }
 }
@@ -1134,6 +1137,14 @@
     // New synonym is derived by applying OpLogicalAnd to |result_id| with the second
     // operand being 'true'.
     LOGICAL_AND = 5;
+
+    // New synonym is derived by applying OpBitwiseOr to |result_id| with the second
+    // operand being 0 taken with the same bit length as |result_id|
+    BITWISE_OR = 6;
+
+    // New synonym is derived by applying OpBitwiseXor to |result_id| with the second
+    // operand being 0 taken with the same bit length as |result_id|
+    BITWISE_XOR = 7;
   }
 
   // Type of the synonym to create. See SynonymType for more details.
@@ -1558,17 +1569,26 @@
 
 message TransformationLoad {
 
-  // Transformation that adds an OpLoad instruction from a pointer into an id.
+  // Transformation that adds an OpLoad or OpAtomicLoad instruction from a pointer into an id.
 
-  // The result of the load instruction
+  // The result of the load instruction.
   uint32 fresh_id = 1;
 
-  // The pointer to be loaded from
+  // The pointer to be loaded from.
   uint32 pointer_id = 2;
 
+  // True if and only if the load should be atomic.
+  bool is_atomic = 3;
+
+  // The memory scope for the atomic load. Ignored unless |is_atomic| is true.
+  uint32 memory_scope_id = 4;
+
+  // The memory semantics for the atomic load. Ignored unless |is_atomic| is true.
+  uint32 memory_semantics_id = 5;
+
   // A descriptor for an instruction in a block before which the new OpLoad
-  // instruction should be inserted
-  InstructionDescriptor instruction_to_insert_before = 3;
+  // instruction should be inserted.
+  InstructionDescriptor instruction_to_insert_before = 6;
 
 }
 
@@ -1632,6 +1652,9 @@
   // A fresh id for the header of the new outer loop.
   uint32 outer_header_id = 2;
 
+  // A fresh id for an unreachable continue construct for the new outer loop.
+  uint32 unreachable_continue_id = 7;
+
   // A fresh id for the new return block of the function,
   // i.e. the merge block of the new outer loop.
   uint32 outer_return_id = 3;
@@ -2222,17 +2245,26 @@
 
 message TransformationStore {
 
-  // Transformation that adds an OpStore instruction of an id to a pointer.
+  // Transformation that adds an OpStore or OpAtomicStore instruction of an id to a pointer.
 
-  // The pointer to be stored to
+  // The pointer to be stored to.
   uint32 pointer_id = 1;
 
-  // The value to be stored
-  uint32 value_id = 2;
+  // True if and only if the load should be atomic.
+  bool is_atomic = 2;
+
+  // The memory scope for the atomic load. Ignored unless |is_atomic| is true.
+  uint32 memory_scope_id = 3;
+
+  // The memory semantics for the atomic load. Ignored unless |is_atomic| is true.
+  uint32 memory_semantics_id = 4;
+
+  // The value to be stored.
+  uint32 value_id = 5;
 
   // A descriptor for an instruction in a block before which the new OpStore
-  // instruction should be inserted
-  InstructionDescriptor instruction_to_insert_before = 3;
+  // instruction should be inserted.
+  InstructionDescriptor instruction_to_insert_before = 6;
 
 }
 
@@ -2260,6 +2292,24 @@
 
 }
 
+message TransformationSwapFunctionVariables {
+  // A transformation that swaps function variables
+
+  // Result id of the first variable.
+  uint32 result_id1 = 1;
+  // Result id of the second variable.
+  uint32 result_id2 = 2;
+
+}
+
+message TransformationSwapTwoFunctions {
+  // A transformation that swaps the position of two functions within the same module.
+
+  // the IDs for the two functions that are swapped.
+  uint32 function_id1 = 1;
+  uint32 function_id2 = 2;
+}
+
 message TransformationToggleAccessChainInstruction {
 
   // A transformation that toggles an access chain instruction.
@@ -2340,3 +2390,39 @@
   bool branch_condition = 3;
 
 }
+
+message TransformationWrapVectorSynonym {
+  // A transformation that wraps an arithmetic operation into a vector operation
+  // and get the result of the original operation from the corresponding index.
+  // For instance, for this transformation, an scalar operation between two scalars:
+  // define op ∈ {+, -, *}
+  // c = a op b
+  //
+  // requires the availability of two vectors:
+  //
+  // va = vector(..., a, ...)
+  // vb = vector(..., b, ...)
+  //
+  // where a and b are in the same position i in each of their correponding vector
+  // and a is synonymous with va[i] and b is synonymous with vb[i].
+  //
+  // The transformation then add an instruction vc = va op vb where c is synonymous
+  // with vc[i].
+
+  // The result if of the original scalar operation instruction.
+  uint32 instruction_id = 1;
+
+  // The result id for the first vector that contains the first value of the scalar operation.
+  uint32 vector_operand1 = 2;
+
+  // The result id for the second vector that contains the second value of the scalar operation.
+  uint32 vector_operand2 = 3;
+
+  // A fresh id for the resulted vector from the addition of the first and second vector.
+  uint32 fresh_id = 4;
+
+  // The position in the vector where the value of original instruction is located. Must be in
+  // the corresponding vector range.
+  uint32 scalar_position = 5;
+
+}
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index ebbc393..70a302b 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -98,10 +98,13 @@
 #include "source/fuzz/transformation_store.h"
 #include "source/fuzz/transformation_swap_commutable_operands.h"
 #include "source/fuzz/transformation_swap_conditional_branch_operands.h"
+#include "source/fuzz/transformation_swap_function_variables.h"
+#include "source/fuzz/transformation_swap_two_functions.h"
 #include "source/fuzz/transformation_toggle_access_chain_instruction.h"
 #include "source/fuzz/transformation_vector_shuffle.h"
 #include "source/fuzz/transformation_wrap_early_terminator_in_function.h"
 #include "source/fuzz/transformation_wrap_region_in_selection.h"
+#include "source/fuzz/transformation_wrap_vector_synonym.h"
 #include "source/util/make_unique.h"
 
 namespace spvtools {
@@ -361,6 +364,12 @@
         kSwapConditionalBranchOperands:
       return MakeUnique<TransformationSwapConditionalBranchOperands>(
           message.swap_conditional_branch_operands());
+    case protobufs::Transformation::TransformationCase::kSwapFunctionVariables:
+      return MakeUnique<TransformationSwapFunctionVariables>(
+          message.swap_function_variables());
+    case protobufs::Transformation::TransformationCase::kSwapTwoFunctions:
+      return MakeUnique<TransformationSwapTwoFunctions>(
+          message.swap_two_functions());
     case protobufs::Transformation::TransformationCase::
         kToggleAccessChainInstruction:
       return MakeUnique<TransformationToggleAccessChainInstruction>(
@@ -374,6 +383,9 @@
     case protobufs::Transformation::TransformationCase::kWrapRegionInSelection:
       return MakeUnique<TransformationWrapRegionInSelection>(
           message.wrap_region_in_selection());
+    case protobufs::Transformation::TransformationCase::kWrapVectorSynonym:
+      return MakeUnique<TransformationWrapVectorSynonym>(
+          message.wrap_vector_synonym());
     case protobufs::Transformation::TRANSFORMATION_NOT_SET:
       assert(false && "An unset transformation was encountered.");
       return nullptr;
diff --git a/source/fuzz/transformation_access_chain.cpp b/source/fuzz/transformation_access_chain.cpp
index daf73d6..3fe9e65 100644
--- a/source/fuzz/transformation_access_chain.cpp
+++ b/source/fuzz/transformation_access_chain.cpp
@@ -23,8 +23,8 @@
 namespace fuzz {
 
 TransformationAccessChain::TransformationAccessChain(
-    const spvtools::fuzz::protobufs::TransformationAccessChain& message)
-    : message_(message) {}
+    protobufs::TransformationAccessChain message)
+    : message_(std::move(message)) {}
 
 TransformationAccessChain::TransformationAccessChain(
     uint32_t fresh_id, uint32_t pointer_id,
@@ -122,7 +122,7 @@
 
       bool successful;
       std::tie(successful, index_value) =
-          GetIndexValue(ir_context, index_id, subobject_type_id);
+          GetStructIndexValue(ir_context, index_id, subobject_type_id);
 
       if (!successful) {
         return false;
@@ -228,6 +228,11 @@
 
   uint32_t id_pairs_used = 0;
 
+  opt::Instruction* instruction_to_insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  opt::BasicBlock* enclosing_block =
+      ir_context->get_instr_block(instruction_to_insert_before);
+
   // Go through the index ids in turn.
   for (auto index_id : message_.index_id()) {
     uint32_t index_value;
@@ -242,7 +247,7 @@
       // It is a struct: we need to retrieve the integer value.
 
       index_value =
-          GetIndexValue(ir_context, index_id, subobject_type_id).second;
+          GetStructIndexValue(ir_context, index_id, subobject_type_id).second;
 
       new_index_id = index_id;
 
@@ -280,29 +285,37 @@
 
       // Clamp the integer and add the corresponding instructions in the module
       // if |add_clamping_instructions| is set.
-      auto instruction_to_insert_before =
-          FindInstruction(message_.instruction_to_insert_before(), ir_context);
 
       // Compare the index with the bound via an instruction of the form:
       //   %fresh_ids.first = OpULessThanEqual %bool %int_id %bound_minus_one.
       fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.first());
-      instruction_to_insert_before->InsertBefore(MakeUnique<opt::Instruction>(
+      auto comparison_instruction = MakeUnique<opt::Instruction>(
           ir_context, SpvOpULessThanEqual, bool_type_id, fresh_ids.first(),
           opt::Instruction::OperandList(
               {{SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}},
-               {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
+               {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}));
+      auto comparison_instruction_ptr = comparison_instruction.get();
+      instruction_to_insert_before->InsertBefore(
+          std::move(comparison_instruction));
+      ir_context->get_def_use_mgr()->AnalyzeInstDefUse(
+          comparison_instruction_ptr);
+      ir_context->set_instr_block(comparison_instruction_ptr, enclosing_block);
 
       // Select the index if in-bounds, otherwise one less than the bound:
       //   %fresh_ids.second = OpSelect %int_type %fresh_ids.first %int_id
       //                           %bound_minus_one
       fuzzerutil::UpdateModuleIdBound(ir_context, fresh_ids.second());
-      instruction_to_insert_before->InsertBefore(MakeUnique<opt::Instruction>(
+      auto select_instruction = MakeUnique<opt::Instruction>(
           ir_context, SpvOpSelect, int_type_inst->result_id(),
           fresh_ids.second(),
           opt::Instruction::OperandList(
               {{SPV_OPERAND_TYPE_ID, {fresh_ids.first()}},
                {SPV_OPERAND_TYPE_ID, {index_instruction->result_id()}},
-               {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}})));
+               {SPV_OPERAND_TYPE_ID, {bound_minus_one_id}}}));
+      auto select_instruction_ptr = select_instruction.get();
+      instruction_to_insert_before->InsertBefore(std::move(select_instruction));
+      ir_context->get_def_use_mgr()->AnalyzeInstDefUse(select_instruction_ptr);
+      ir_context->set_instr_block(select_instruction_ptr, enclosing_block);
 
       new_index_id = fresh_ids.second();
 
@@ -326,13 +339,14 @@
   // Add the access chain instruction to the module, and update the module's
   // id bound.
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  FindInstruction(message_.instruction_to_insert_before(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
-          ir_context, SpvOpAccessChain, result_type, message_.fresh_id(),
-          operands));
-
-  // Conservatively invalidate all analyses.
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  auto access_chain_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpAccessChain, result_type, message_.fresh_id(), operands);
+  auto access_chain_instruction_ptr = access_chain_instruction.get();
+  instruction_to_insert_before->InsertBefore(
+      std::move(access_chain_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(
+      access_chain_instruction_ptr);
+  ir_context->set_instr_block(access_chain_instruction_ptr, enclosing_block);
 
   // If the base pointer's pointee value was irrelevant, the same is true of
   // the pointee value of the result of this access chain.
@@ -349,9 +363,12 @@
   return result;
 }
 
-std::pair<bool, uint32_t> TransformationAccessChain::GetIndexValue(
+std::pair<bool, uint32_t> TransformationAccessChain::GetStructIndexValue(
     opt::IRContext* ir_context, uint32_t index_id,
     uint32_t object_type_id) const {
+  assert(ir_context->get_def_use_mgr()->GetDef(object_type_id)->opcode() ==
+             SpvOpTypeStruct &&
+         "Precondition: the type must be a struct type.");
   if (!ValidIndexToComposite(ir_context, index_id, object_type_id)) {
     return {false, 0};
   }
@@ -360,10 +377,9 @@
   uint32_t bound = fuzzerutil::GetBoundForCompositeIndex(
       *ir_context->get_def_use_mgr()->GetDef(object_type_id), ir_context);
 
-  // The index must be a constant
-  if (!spvOpcodeIsConstant(index_instruction->opcode())) {
-    return {false, 0};
-  }
+  // Ensure that the index given must represent a constant.
+  assert(spvOpcodeIsConstant(index_instruction->opcode()) &&
+         "A non-constant index should already have been rejected.");
 
   // The index must be in bounds.
   uint32_t value = index_instruction->GetSingleWordInOperand(0);
diff --git a/source/fuzz/transformation_access_chain.h b/source/fuzz/transformation_access_chain.h
index 02cdc32..4e4fd2b 100644
--- a/source/fuzz/transformation_access_chain.h
+++ b/source/fuzz/transformation_access_chain.h
@@ -28,7 +28,7 @@
 class TransformationAccessChain : public Transformation {
  public:
   explicit TransformationAccessChain(
-      const protobufs::TransformationAccessChain& message);
+      protobufs::TransformationAccessChain message);
 
   TransformationAccessChain(
       uint32_t fresh_id, uint32_t pointer_id,
@@ -83,13 +83,13 @@
  private:
   // Returns {false, 0} in each of the following cases:
   // - |index_id| does not correspond to a 32-bit integer constant
-  // - the object being indexed is not a composite type
+  // - |object_type_id| must be a struct type
   // - the constant at |index_id| is out of bounds.
   // Otherwise, returns {true, value}, where value is the value of the constant
   // at |index_id|.
-  std::pair<bool, uint32_t> GetIndexValue(opt::IRContext* ir_context,
-                                          uint32_t index_id,
-                                          uint32_t object_type_id) const;
+  std::pair<bool, uint32_t> GetStructIndexValue(opt::IRContext* ir_context,
+                                                uint32_t index_id,
+                                                uint32_t object_type_id) const;
 
   // Returns true if |index_id| corresponds, in the given context, to a 32-bit
   // integer which can be used to index an object of the type specified by
diff --git a/source/fuzz/transformation_add_bit_instruction_synonym.cpp b/source/fuzz/transformation_add_bit_instruction_synonym.cpp
index 6cdfdfb..636c0a3 100644
--- a/source/fuzz/transformation_add_bit_instruction_synonym.cpp
+++ b/source/fuzz/transformation_add_bit_instruction_synonym.cpp
@@ -21,9 +21,8 @@
 namespace fuzz {
 
 TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym(
-    const spvtools::fuzz::protobufs::TransformationAddBitInstructionSynonym&
-        message)
-    : message_(message) {}
+    protobufs::TransformationAddBitInstructionSynonym message)
+    : message_(std::move(message)) {}
 
 TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym(
     const uint32_t instruction_result_id,
@@ -40,20 +39,9 @@
   auto instruction =
       ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
 
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557):
-  //  Right now we only support certain operations. When this issue is addressed
-  //  the following conditional can use the function |spvOpcodeIsBit|.
-  // |instruction| must be defined and must be a supported bit instruction.
-  if (!instruction || (instruction->opcode() != SpvOpBitwiseOr &&
-                       instruction->opcode() != SpvOpBitwiseXor &&
-                       instruction->opcode() != SpvOpBitwiseAnd &&
-                       instruction->opcode() != SpvOpNot)) {
-    return false;
-  }
-
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3792):
-  //  Right now, only integer operands are supported.
-  if (ir_context->get_type_mgr()->GetType(instruction->type_id())->AsVector()) {
+  // Checks on: only integer operands are supported, instructions are bitwise
+  // operations only. Signedness of the operands must be the same.
+  if (!IsInstructionSupported(ir_context, instruction)) {
     return false;
   }
 
@@ -112,6 +100,65 @@
   }
 }
 
+bool TransformationAddBitInstructionSynonym::IsInstructionSupported(
+    opt::IRContext* ir_context, opt::Instruction* instruction) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557):
+  //  Right now we only support certain operations. When this issue is addressed
+  //  the following conditional can use the function |spvOpcodeIsBit|.
+  // |instruction| must be defined and must be a supported bit instruction.
+  if (!instruction || (instruction->opcode() != SpvOpBitwiseOr &&
+                       instruction->opcode() != SpvOpBitwiseXor &&
+                       instruction->opcode() != SpvOpBitwiseAnd &&
+                       instruction->opcode() != SpvOpNot)) {
+    return false;
+  }
+
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3792):
+  //  Right now, only integer operands are supported.
+  if (ir_context->get_type_mgr()->GetType(instruction->type_id())->AsVector()) {
+    return false;
+  }
+
+  if (instruction->opcode() == SpvOpNot) {
+    auto operand = instruction->GetInOperand(0).words[0];
+    auto operand_inst = ir_context->get_def_use_mgr()->GetDef(operand);
+    auto operand_type =
+        ir_context->get_type_mgr()->GetType(operand_inst->type_id());
+    auto operand_sign = operand_type->AsInteger()->IsSigned();
+
+    auto type_id_sign = ir_context->get_type_mgr()
+                            ->GetType(instruction->type_id())
+                            ->AsInteger()
+                            ->IsSigned();
+
+    return operand_sign == type_id_sign;
+
+  } else {
+    // Other BitWise operations that takes two operands.
+    auto first_operand = instruction->GetInOperand(0).words[0];
+    auto first_operand_inst =
+        ir_context->get_def_use_mgr()->GetDef(first_operand);
+    auto first_operand_type =
+        ir_context->get_type_mgr()->GetType(first_operand_inst->type_id());
+    auto first_operand_sign = first_operand_type->AsInteger()->IsSigned();
+
+    auto second_operand = instruction->GetInOperand(1).words[0];
+    auto second_operand_inst =
+        ir_context->get_def_use_mgr()->GetDef(second_operand);
+    auto second_operand_type =
+        ir_context->get_type_mgr()->GetType(second_operand_inst->type_id());
+    auto second_operand_sign = second_operand_type->AsInteger()->IsSigned();
+
+    auto type_id_sign = ir_context->get_type_mgr()
+                            ->GetType(instruction->type_id())
+                            ->AsInteger()
+                            ->IsSigned();
+
+    return first_operand_sign == second_operand_sign &&
+           first_operand_sign == type_id_sign;
+  }
+}
+
 protobufs::Transformation TransformationAddBitInstructionSynonym::ToMessage()
     const {
   protobufs::Transformation result;
diff --git a/source/fuzz/transformation_add_bit_instruction_synonym.h b/source/fuzz/transformation_add_bit_instruction_synonym.h
index ed1a0af..40b947a 100644
--- a/source/fuzz/transformation_add_bit_instruction_synonym.h
+++ b/source/fuzz/transformation_add_bit_instruction_synonym.h
@@ -103,7 +103,7 @@
 class TransformationAddBitInstructionSynonym : public Transformation {
  public:
   explicit TransformationAddBitInstructionSynonym(
-      const protobufs::TransformationAddBitInstructionSynonym& message);
+      protobufs::TransformationAddBitInstructionSynonym message);
 
   TransformationAddBitInstructionSynonym(
       const uint32_t instruction_result_id,
@@ -128,6 +128,14 @@
   static uint32_t GetRequiredFreshIdCount(opt::IRContext* ir_context,
                                           opt::Instruction* bit_instruction);
 
+  //   Returns true if:
+  // - A |bit_instruction| is one of OpBitwiseOr, OpBitwiseAnd, OpBitwiseXor or
+  // OpNot.
+  // - |bit_instruction|'s operands are scalars.
+  // - The operands have the same signedness.
+  static bool IsInstructionSupported(opt::IRContext* ir_context,
+                                     opt::Instruction* instruction);
+
  private:
   protobufs::TransformationAddBitInstructionSynonym message_;
 
diff --git a/source/fuzz/transformation_add_constant_boolean.cpp b/source/fuzz/transformation_add_constant_boolean.cpp
index 937fdbc..3935432 100644
--- a/source/fuzz/transformation_add_constant_boolean.cpp
+++ b/source/fuzz/transformation_add_constant_boolean.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationAddConstantBoolean::TransformationAddConstantBoolean(
-    const protobufs::TransformationAddConstantBoolean& message)
-    : message_(message) {}
+    protobufs::TransformationAddConstantBoolean message)
+    : message_(std::move(message)) {}
 
 TransformationAddConstantBoolean::TransformationAddConstantBoolean(
     uint32_t fresh_id, bool is_true, bool is_irrelevant) {
@@ -42,14 +42,18 @@
     TransformationContext* transformation_context) const {
   // Add the boolean constant to the module, ensuring the module's id bound is
   // high enough.
+  auto new_instruction = MakeUnique<opt::Instruction>(
+      ir_context, message_.is_true() ? SpvOpConstantTrue : SpvOpConstantFalse,
+      fuzzerutil::MaybeGetBoolType(ir_context), message_.fresh_id(),
+      opt::Instruction::OperandList());
+  auto new_instruction_ptr = new_instruction.get();
+  ir_context->module()->AddGlobalValue(std::move(new_instruction));
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  ir_context->module()->AddGlobalValue(
-      message_.is_true() ? SpvOpConstantTrue : SpvOpConstantFalse,
-      message_.fresh_id(), fuzzerutil::MaybeGetBoolType(ir_context));
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Inform the def-use manager about the new instruction. Invalidate the
+  // constant manager as we have added a new constant.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(new_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisConstants);
 
   if (message_.is_irrelevant()) {
     transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
diff --git a/source/fuzz/transformation_add_constant_boolean.h b/source/fuzz/transformation_add_constant_boolean.h
index d1d04ef..7f31471 100644
--- a/source/fuzz/transformation_add_constant_boolean.h
+++ b/source/fuzz/transformation_add_constant_boolean.h
@@ -26,7 +26,7 @@
 class TransformationAddConstantBoolean : public Transformation {
  public:
   explicit TransformationAddConstantBoolean(
-      const protobufs::TransformationAddConstantBoolean& message);
+      protobufs::TransformationAddConstantBoolean message);
 
   TransformationAddConstantBoolean(uint32_t fresh_id, bool is_true,
                                    bool is_irrelevant);
diff --git a/source/fuzz/transformation_add_constant_composite.cpp b/source/fuzz/transformation_add_constant_composite.cpp
index 0260321..e6cd5a9 100644
--- a/source/fuzz/transformation_add_constant_composite.cpp
+++ b/source/fuzz/transformation_add_constant_composite.cpp
@@ -22,9 +22,8 @@
 namespace fuzz {
 
 TransformationAddConstantComposite::TransformationAddConstantComposite(
-    const spvtools::fuzz::protobufs::TransformationAddConstantComposite&
-        message)
-    : message_(message) {}
+    spvtools::fuzz::protobufs::TransformationAddConstantComposite message)
+    : message_(std::move(message)) {}
 
 TransformationAddConstantComposite::TransformationAddConstantComposite(
     uint32_t fresh_id, uint32_t type_id,
@@ -120,14 +119,17 @@
   for (auto constituent_id : message_.constituent_id()) {
     in_operands.push_back({SPV_OPERAND_TYPE_ID, {constituent_id}});
   }
-  ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+  auto new_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpConstantComposite, message_.type_id(),
-      message_.fresh_id(), in_operands));
+      message_.fresh_id(), in_operands);
+  auto new_instruction_ptr = new_instruction.get();
+  ir_context->module()->AddGlobalValue(std::move(new_instruction));
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Inform the def-use manager of the new instruction. Invalidate the constant
+  // manager as we have added a new constant.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisConstants);
 
   if (message_.is_irrelevant()) {
     transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
diff --git a/source/fuzz/transformation_add_constant_composite.h b/source/fuzz/transformation_add_constant_composite.h
index 9e9222d..94e7a92 100644
--- a/source/fuzz/transformation_add_constant_composite.h
+++ b/source/fuzz/transformation_add_constant_composite.h
@@ -28,7 +28,7 @@
 class TransformationAddConstantComposite : public Transformation {
  public:
   explicit TransformationAddConstantComposite(
-      const protobufs::TransformationAddConstantComposite& message);
+      protobufs::TransformationAddConstantComposite message);
 
   TransformationAddConstantComposite(
       uint32_t fresh_id, uint32_t type_id,
diff --git a/source/fuzz/transformation_add_constant_null.cpp b/source/fuzz/transformation_add_constant_null.cpp
index 3c66ab1..c0f7367 100644
--- a/source/fuzz/transformation_add_constant_null.cpp
+++ b/source/fuzz/transformation_add_constant_null.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddConstantNull::TransformationAddConstantNull(
-    const spvtools::fuzz::protobufs::TransformationAddConstantNull& message)
-    : message_(message) {}
+    spvtools::fuzz::protobufs::TransformationAddConstantNull message)
+    : message_(std::move(message)) {}
 
 TransformationAddConstantNull::TransformationAddConstantNull(uint32_t fresh_id,
                                                              uint32_t type_id) {
@@ -35,25 +35,29 @@
   if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) {
     return false;
   }
-  auto type = context->get_type_mgr()->GetType(message_.type_id());
+  auto type = context->get_def_use_mgr()->GetDef(message_.type_id());
   // The type must exist.
   if (!type) {
     return false;
   }
   // The type must be one of the types for which null constants are allowed,
   // according to the SPIR-V spec.
-  return fuzzerutil::IsNullConstantSupported(*type);
+  return fuzzerutil::IsNullConstantSupported(context, *type);
 }
 
 void TransformationAddConstantNull::Apply(
-    opt::IRContext* context, TransformationContext* /*unused*/) const {
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context, SpvOpConstantNull, message_.type_id(), message_.fresh_id(),
-      opt::Instruction::OperandList()));
-  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);
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto new_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpConstantNull, message_.type_id(), message_.fresh_id(),
+      opt::Instruction::OperandList());
+  auto new_instruction_ptr = new_instruction.get();
+  ir_context->module()->AddGlobalValue(std::move(new_instruction));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Inform the def-use manager about the new instruction. Invalidate the
+  // constant manager as we have added a new constant.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(new_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisConstants);
 }
 
 protobufs::Transformation TransformationAddConstantNull::ToMessage() const {
diff --git a/source/fuzz/transformation_add_constant_null.h b/source/fuzz/transformation_add_constant_null.h
index bd08b1d..bb1d1b7 100644
--- a/source/fuzz/transformation_add_constant_null.h
+++ b/source/fuzz/transformation_add_constant_null.h
@@ -26,7 +26,7 @@
 class TransformationAddConstantNull : public Transformation {
  public:
   explicit TransformationAddConstantNull(
-      const protobufs::TransformationAddConstantNull& message);
+      protobufs::TransformationAddConstantNull message);
 
   TransformationAddConstantNull(uint32_t fresh_id, uint32_t type_id);
 
@@ -34,12 +34,12 @@
   // - |message_.type_id| must be the id of a type for which it is acceptable
   //   to create a null constant
   bool IsApplicable(
-      opt::IRContext* context,
+      opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
 
   // Adds an OpConstantNull instruction to the module, with |message_.type_id|
   // as its type.  The instruction has result id |message_.fresh_id|.
-  void Apply(opt::IRContext* context,
+  void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
   std::unordered_set<uint32_t> GetFreshIds() const override;
diff --git a/source/fuzz/transformation_add_constant_scalar.cpp b/source/fuzz/transformation_add_constant_scalar.cpp
index 9a6642a..a2d95fb 100644
--- a/source/fuzz/transformation_add_constant_scalar.cpp
+++ b/source/fuzz/transformation_add_constant_scalar.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddConstantScalar::TransformationAddConstantScalar(
-    const spvtools::fuzz::protobufs::TransformationAddConstantScalar& message)
-    : message_(message) {}
+    spvtools::fuzz::protobufs::TransformationAddConstantScalar message)
+    : message_(std::move(message)) {}
 
 TransformationAddConstantScalar::TransformationAddConstantScalar(
     uint32_t fresh_id, uint32_t type_id, const std::vector<uint32_t>& words,
@@ -64,19 +64,21 @@
 void TransformationAddConstantScalar::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+  auto new_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpConstant, message_.type_id(), message_.fresh_id(),
       opt::Instruction::OperandList(
           {{SPV_OPERAND_TYPE_LITERAL_INTEGER,
             std::vector<uint32_t>(message_.word().begin(),
-                                  message_.word().end())}})));
+                                  message_.word().end())}}));
+  auto new_instruction_ptr = new_instruction.get();
+  ir_context->module()->AddGlobalValue(std::move(new_instruction));
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  // Inform the def-use manager about the new instruction. Invalidate the
+  // constant manager as we have added a new constant.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(new_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisConstants);
 
   if (message_.is_irrelevant()) {
     transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
diff --git a/source/fuzz/transformation_add_constant_scalar.h b/source/fuzz/transformation_add_constant_scalar.h
index 3f23907..adb0735 100644
--- a/source/fuzz/transformation_add_constant_scalar.h
+++ b/source/fuzz/transformation_add_constant_scalar.h
@@ -28,7 +28,7 @@
 class TransformationAddConstantScalar : public Transformation {
  public:
   explicit TransformationAddConstantScalar(
-      const protobufs::TransformationAddConstantScalar& message);
+      protobufs::TransformationAddConstantScalar message);
 
   TransformationAddConstantScalar(uint32_t fresh_id, uint32_t type_id,
                                   const std::vector<uint32_t>& words,
diff --git a/source/fuzz/transformation_add_copy_memory.cpp b/source/fuzz/transformation_add_copy_memory.cpp
index 44bb9c5..5eb4bdc 100644
--- a/source/fuzz/transformation_add_copy_memory.cpp
+++ b/source/fuzz/transformation_add_copy_memory.cpp
@@ -22,8 +22,8 @@
 namespace fuzz {
 
 TransformationAddCopyMemory::TransformationAddCopyMemory(
-    const protobufs::TransformationAddCopyMemory& message)
-    : message_(message) {}
+    protobufs::TransformationAddCopyMemory message)
+    : message_(std::move(message)) {}
 
 TransformationAddCopyMemory::TransformationAddCopyMemory(
     const protobufs::InstructionDescriptor& instruction_descriptor,
@@ -99,15 +99,8 @@
   auto* insert_before_inst =
       FindInstruction(message_.instruction_descriptor(), ir_context);
   assert(insert_before_inst);
-
-  auto insert_before_iter = fuzzerutil::GetIteratorForInstruction(
-      ir_context->get_instr_block(insert_before_inst), insert_before_inst);
-
-  insert_before_iter.InsertBefore(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpCopyMemory, 0, 0,
-      opt::Instruction::OperandList{
-          {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}},
-          {SPV_OPERAND_TYPE_ID, {message_.source_id()}}}));
+  opt::BasicBlock* enclosing_block =
+      ir_context->get_instr_block(insert_before_inst);
 
   // Add global or local variable to copy memory into.
   auto storage_class = static_cast<SpvStorageClass>(message_.storage_class());
@@ -118,23 +111,35 @@
       storage_class);
 
   if (storage_class == SpvStorageClassPrivate) {
-    fuzzerutil::AddGlobalVariable(ir_context, message_.fresh_id(), type_id,
-                                  storage_class, message_.initializer_id());
+    opt::Instruction* new_global =
+        fuzzerutil::AddGlobalVariable(ir_context, message_.fresh_id(), type_id,
+                                      storage_class, message_.initializer_id());
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_global);
   } else {
     assert(storage_class == SpvStorageClassFunction &&
            "Storage class can be either Private or Function");
-    fuzzerutil::AddLocalVariable(ir_context, message_.fresh_id(), type_id,
-                                 ir_context->get_instr_block(insert_before_inst)
-                                     ->GetParent()
-                                     ->result_id(),
-                                 message_.initializer_id());
+    opt::Function* enclosing_function = enclosing_block->GetParent();
+    opt::Instruction* new_local = fuzzerutil::AddLocalVariable(
+        ir_context, message_.fresh_id(), type_id,
+        enclosing_function->result_id(), message_.initializer_id());
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_local);
+    ir_context->set_instr_block(new_local, &*enclosing_function->entry());
   }
 
-  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  auto insert_before_iter = fuzzerutil::GetIteratorForInstruction(
+      enclosing_block, insert_before_inst);
 
-  // Make sure our changes are analyzed
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  auto new_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpCopyMemory, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}},
+          {SPV_OPERAND_TYPE_ID, {message_.source_id()}}});
+  auto new_instruction_ptr = new_instruction.get();
+  insert_before_iter.InsertBefore(std::move(new_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+  ir_context->set_instr_block(new_instruction_ptr, enclosing_block);
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
   // Even though the copy memory instruction will - at least temporarily - lead
   // to the destination and source pointers referring to identical values, this
diff --git a/source/fuzz/transformation_add_copy_memory.h b/source/fuzz/transformation_add_copy_memory.h
index cc42f1e..b25652f 100644
--- a/source/fuzz/transformation_add_copy_memory.h
+++ b/source/fuzz/transformation_add_copy_memory.h
@@ -26,7 +26,7 @@
 class TransformationAddCopyMemory : public Transformation {
  public:
   explicit TransformationAddCopyMemory(
-      const protobufs::TransformationAddCopyMemory& message);
+      protobufs::TransformationAddCopyMemory message);
 
   TransformationAddCopyMemory(
       const protobufs::InstructionDescriptor& instruction_descriptor,
diff --git a/source/fuzz/transformation_add_dead_block.cpp b/source/fuzz/transformation_add_dead_block.cpp
index 5dce356..df700ce 100644
--- a/source/fuzz/transformation_add_dead_block.cpp
+++ b/source/fuzz/transformation_add_dead_block.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddDeadBlock::TransformationAddDeadBlock(
-    const spvtools::fuzz::protobufs::TransformationAddDeadBlock& message)
-    : message_(message) {}
+    protobufs::TransformationAddDeadBlock message)
+    : message_(std::move(message)) {}
 
 TransformationAddDeadBlock::TransformationAddDeadBlock(uint32_t fresh_id,
                                                        uint32_t existing_block,
@@ -79,9 +79,7 @@
   }
 
   // |existing_block| must be reachable.
-  opt::DominatorAnalysis* dominator_analysis =
-      ir_context->GetDominatorAnalysis(existing_block->GetParent());
-  if (!dominator_analysis->IsReachable(existing_block->id())) {
+  if (!ir_context->IsReachable(*existing_block)) {
     return false;
   }
 
@@ -94,6 +92,8 @@
   // the selection construct, its header |existing_block| will not dominate the
   // merge block |successor_block_id|, which is invalid. Thus, |existing_block|
   // must dominate |successor_block_id|.
+  opt::DominatorAnalysis* dominator_analysis =
+      ir_context->GetDominatorAnalysis(existing_block->GetParent());
   if (!dominator_analysis->Dominates(existing_block->id(),
                                      successor_block_id)) {
     return false;
diff --git a/source/fuzz/transformation_add_dead_block.h b/source/fuzz/transformation_add_dead_block.h
index 50af6b0..d8b3c2a 100644
--- a/source/fuzz/transformation_add_dead_block.h
+++ b/source/fuzz/transformation_add_dead_block.h
@@ -26,7 +26,7 @@
 class TransformationAddDeadBlock : public Transformation {
  public:
   explicit TransformationAddDeadBlock(
-      const protobufs::TransformationAddDeadBlock& message);
+      protobufs::TransformationAddDeadBlock message);
 
   TransformationAddDeadBlock(uint32_t fresh_id, uint32_t existing_block,
                              bool condition_value);
diff --git a/source/fuzz/transformation_add_dead_break.cpp b/source/fuzz/transformation_add_dead_break.cpp
index bc938d4..32080ca 100644
--- a/source/fuzz/transformation_add_dead_break.cpp
+++ b/source/fuzz/transformation_add_dead_break.cpp
@@ -24,8 +24,8 @@
 namespace fuzz {
 
 TransformationAddDeadBreak::TransformationAddDeadBreak(
-    const spvtools::fuzz::protobufs::TransformationAddDeadBreak& message)
-    : message_(message) {}
+    protobufs::TransformationAddDeadBreak message)
+    : message_(std::move(message)) {}
 
 TransformationAddDeadBreak::TransformationAddDeadBreak(
     uint32_t from_block, uint32_t to_block, bool break_condition_value,
@@ -112,9 +112,10 @@
     const TransformationContext& transformation_context) const {
   // First, we check that a constant with the same value as
   // |message_.break_condition_value| is present.
-  if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
-                                        message_.break_condition_value(),
-                                        false)) {
+  const auto bool_id =
+      fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
+                                       message_.break_condition_value(), false);
+  if (!bool_id) {
     // The required constant is not present, so the transformation cannot be
     // applied.
     return false;
@@ -133,7 +134,7 @@
     return false;
   }
 
-  if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, bb_to)) {
+  if (!ir_context->IsReachable(*bb_to)) {
     // If the target of the break is unreachable, we conservatively do not
     // allow adding a dead break, to avoid the compilations that arise due to
     // the lack of sensible dominance information for unreachable blocks.
@@ -171,25 +172,23 @@
   }
 
   // Adding the dead break is only valid if SPIR-V rules related to dominance
-  // hold.  Rather than checking these rules explicitly, we defer to the
-  // validator.  We make a clone of the module, apply the transformation to the
-  // clone, and check whether the transformed clone is valid.
-  //
-  // In principle some of the above checks could be removed, with more reliance
-  // being places on the validator.  This should be revisited if we are sure
-  // the validator is complete with respect to checking structured control flow
-  // rules.
-  auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
-  ApplyImpl(cloned_context.get(), transformation_context);
-  return fuzzerutil::IsValid(cloned_context.get(),
-                             transformation_context.GetValidatorOptions(),
-                             fuzzerutil::kSilentMessageConsumer);
+  // hold.
+  return fuzzerutil::NewTerminatorPreservesDominationRules(
+      ir_context, message_.from_block(),
+      fuzzerutil::CreateUnreachableEdgeInstruction(
+          ir_context, message_.from_block(), message_.to_block(), bool_id));
 }
 
 void TransformationAddDeadBreak::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  ApplyImpl(ir_context, *transformation_context);
+  fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
+      ir_context, ir_context->cfg()->block(message_.from_block()),
+      ir_context->cfg()->block(message_.to_block()),
+      fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
+                                       message_.break_condition_value(), false),
+      message_.phi_id());
+
   // Invalidate all analyses
   ir_context->InvalidateAnalysesExceptFor(
       opt::IRContext::Analysis::kAnalysisNone);
@@ -201,17 +200,6 @@
   return result;
 }
 
-void TransformationAddDeadBreak::ApplyImpl(
-    spvtools::opt::IRContext* ir_context,
-    const TransformationContext& transformation_context) const {
-  fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
-      ir_context, ir_context->cfg()->block(message_.from_block()),
-      ir_context->cfg()->block(message_.to_block()),
-      fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
-                                       message_.break_condition_value(), false),
-      message_.phi_id());
-}
-
 std::unordered_set<uint32_t> TransformationAddDeadBreak::GetFreshIds() const {
   return std::unordered_set<uint32_t>();
 }
diff --git a/source/fuzz/transformation_add_dead_break.h b/source/fuzz/transformation_add_dead_break.h
index afb8dc7..8c1ab4a 100644
--- a/source/fuzz/transformation_add_dead_break.h
+++ b/source/fuzz/transformation_add_dead_break.h
@@ -28,7 +28,7 @@
 class TransformationAddDeadBreak : public Transformation {
  public:
   explicit TransformationAddDeadBreak(
-      const protobufs::TransformationAddDeadBreak& message);
+      protobufs::TransformationAddDeadBreak message);
 
   TransformationAddDeadBreak(uint32_t from_block, uint32_t to_block,
                              bool break_condition_value,
@@ -71,15 +71,6 @@
   bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* ir_context,
                                                 opt::BasicBlock* bb_from) const;
 
-  // Used by 'Apply' to actually apply the transformation to the module of
-  // interest, and by 'IsApplicable' to do a dry-run of the transformation on a
-  // cloned module, in order to check that the transformation leads to a valid
-  // module.  This is only invoked by 'IsApplicable' after certain basic
-  // applicability checks have been made, ensuring that the invocation of this
-  // method is legal.
-  void ApplyImpl(opt::IRContext* ir_context,
-                 const TransformationContext& transformation_context) const;
-
   protobufs::TransformationAddDeadBreak message_;
 };
 
diff --git a/source/fuzz/transformation_add_dead_continue.cpp b/source/fuzz/transformation_add_dead_continue.cpp
index 18b3c39..f2b9ab3 100644
--- a/source/fuzz/transformation_add_dead_continue.cpp
+++ b/source/fuzz/transformation_add_dead_continue.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddDeadContinue::TransformationAddDeadContinue(
-    const spvtools::fuzz::protobufs::TransformationAddDeadContinue& message)
-    : message_(message) {}
+    protobufs::TransformationAddDeadContinue message)
+    : message_(std::move(message)) {}
 
 TransformationAddDeadContinue::TransformationAddDeadContinue(
     uint32_t from_block, bool continue_condition_value,
@@ -38,9 +38,10 @@
     const TransformationContext& transformation_context) const {
   // First, we check that a constant with the same value as
   // |message_.continue_condition_value| is present.
-  if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
-                                        message_.continue_condition_value(),
-                                        false)) {
+  const auto bool_id = fuzzerutil::MaybeGetBoolConstant(
+      ir_context, transformation_context, message_.continue_condition_value(),
+      false);
+  if (!bool_id) {
     // The required constant is not present, so the transformation cannot be
     // applied.
     return false;
@@ -82,8 +83,7 @@
   auto continue_block =
       ir_context->cfg()->block(loop_header)->ContinueBlockId();
 
-  if (!fuzzerutil::BlockIsReachableInItsFunction(
-          ir_context, ir_context->cfg()->block(continue_block))) {
+  if (!ir_context->IsReachable(*ir_context->cfg()->block(continue_block))) {
     // If the loop's continue block is unreachable, we conservatively do not
     // allow adding a dead continue, to avoid the compilations that arise due to
     // the lack of sensible dominance information for unreachable blocks.
@@ -111,39 +111,16 @@
   }
 
   // Adding the dead break is only valid if SPIR-V rules related to dominance
-  // hold.  Rather than checking these rules explicitly, we defer to the
-  // validator.  We make a clone of the module, apply the transformation to the
-  // clone, and check whether the transformed clone is valid.
-  //
-  // In principle some of the above checks could be removed, with more reliance
-  // being placed on the validator.  This should be revisited if we are sure
-  // the validator is complete with respect to checking structured control flow
-  // rules.
-  auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
-  ApplyImpl(cloned_context.get(), transformation_context);
-  return fuzzerutil::IsValid(cloned_context.get(),
-                             transformation_context.GetValidatorOptions(),
-                             fuzzerutil::kSilentMessageConsumer);
+  // hold.
+  return fuzzerutil::NewTerminatorPreservesDominationRules(
+      ir_context, message_.from_block(),
+      fuzzerutil::CreateUnreachableEdgeInstruction(
+          ir_context, message_.from_block(), continue_block, bool_id));
 }
 
 void TransformationAddDeadContinue::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  ApplyImpl(ir_context, *transformation_context);
-  // Invalidate all analyses
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
-}
-
-protobufs::Transformation TransformationAddDeadContinue::ToMessage() const {
-  protobufs::Transformation result;
-  *result.mutable_add_dead_continue() = message_;
-  return result;
-}
-
-void TransformationAddDeadContinue::ApplyImpl(
-    spvtools::opt::IRContext* ir_context,
-    const TransformationContext& transformation_context) const {
   auto bb_from = ir_context->cfg()->block(message_.from_block());
   auto continue_block =
       bb_from->IsLoopHeader()
@@ -153,10 +130,20 @@
   assert(continue_block && "message_.from_block must be in a loop.");
   fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
       ir_context, bb_from, ir_context->cfg()->block(continue_block),
-      fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
+      fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
                                        message_.continue_condition_value(),
                                        false),
       message_.phi_id());
+
+  // Invalidate all analyses
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddDeadContinue::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_dead_continue() = message_;
+  return result;
 }
 
 std::unordered_set<uint32_t> TransformationAddDeadContinue::GetFreshIds()
diff --git a/source/fuzz/transformation_add_dead_continue.h b/source/fuzz/transformation_add_dead_continue.h
index 27527e7..9463aeb 100644
--- a/source/fuzz/transformation_add_dead_continue.h
+++ b/source/fuzz/transformation_add_dead_continue.h
@@ -28,7 +28,7 @@
 class TransformationAddDeadContinue : public Transformation {
  public:
   explicit TransformationAddDeadContinue(
-      const protobufs::TransformationAddDeadContinue& message);
+      protobufs::TransformationAddDeadContinue message);
 
   TransformationAddDeadContinue(uint32_t from_block,
                                 bool continue_condition_value,
@@ -68,15 +68,6 @@
   protobufs::Transformation ToMessage() const override;
 
  private:
-  // Used by 'Apply' to actually apply the transformation to the module of
-  // interest, and by 'IsApplicable' to do a dry-run of the transformation on a
-  // cloned module, in order to check that the transformation leads to a valid
-  // module.  This is only invoked by 'IsApplicable' after certain basic
-  // applicability checks have been made, ensuring that the invocation of this
-  // method is legal.
-  void ApplyImpl(opt::IRContext* ir_context,
-                 const TransformationContext& transformation_context) const;
-
   protobufs::TransformationAddDeadContinue message_;
 };
 
diff --git a/source/fuzz/transformation_add_early_terminator_wrapper.cpp b/source/fuzz/transformation_add_early_terminator_wrapper.cpp
index 9f86070..547398a 100644
--- a/source/fuzz/transformation_add_early_terminator_wrapper.cpp
+++ b/source/fuzz/transformation_add_early_terminator_wrapper.cpp
@@ -22,9 +22,8 @@
 
 TransformationAddEarlyTerminatorWrapper::
     TransformationAddEarlyTerminatorWrapper(
-        const spvtools::fuzz::protobufs::
-            TransformationAddEarlyTerminatorWrapper& message)
-    : message_(message) {}
+        protobufs::TransformationAddEarlyTerminatorWrapper message)
+    : message_(std::move(message)) {}
 
 TransformationAddEarlyTerminatorWrapper::
     TransformationAddEarlyTerminatorWrapper(uint32_t function_fresh_id,
diff --git a/source/fuzz/transformation_add_early_terminator_wrapper.h b/source/fuzz/transformation_add_early_terminator_wrapper.h
index 273037e..97cc527 100644
--- a/source/fuzz/transformation_add_early_terminator_wrapper.h
+++ b/source/fuzz/transformation_add_early_terminator_wrapper.h
@@ -26,7 +26,7 @@
 class TransformationAddEarlyTerminatorWrapper : public Transformation {
  public:
   explicit TransformationAddEarlyTerminatorWrapper(
-      const protobufs::TransformationAddEarlyTerminatorWrapper& message);
+      protobufs::TransformationAddEarlyTerminatorWrapper message);
 
   TransformationAddEarlyTerminatorWrapper(uint32_t function_fresh_id,
                                           uint32_t label_fresh_id,
diff --git a/source/fuzz/transformation_add_function.cpp b/source/fuzz/transformation_add_function.cpp
index 9799373..06cd657 100644
--- a/source/fuzz/transformation_add_function.cpp
+++ b/source/fuzz/transformation_add_function.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationAddFunction::TransformationAddFunction(
-    const spvtools::fuzz::protobufs::TransformationAddFunction& message)
-    : message_(message) {}
+    protobufs::TransformationAddFunction message)
+    : message_(std::move(message)) {}
 
 TransformationAddFunction::TransformationAddFunction(
     const std::vector<protobufs::Instruction>& instructions) {
diff --git a/source/fuzz/transformation_add_function.h b/source/fuzz/transformation_add_function.h
index e5381d1..c41eee3 100644
--- a/source/fuzz/transformation_add_function.h
+++ b/source/fuzz/transformation_add_function.h
@@ -26,7 +26,7 @@
 class TransformationAddFunction : public Transformation {
  public:
   explicit TransformationAddFunction(
-      const protobufs::TransformationAddFunction& message);
+      protobufs::TransformationAddFunction message);
 
   // Creates a transformation to add a non live-safe function.
   explicit TransformationAddFunction(
diff --git a/source/fuzz/transformation_add_global_undef.cpp b/source/fuzz/transformation_add_global_undef.cpp
index 7a90b82..ec0574a 100644
--- a/source/fuzz/transformation_add_global_undef.cpp
+++ b/source/fuzz/transformation_add_global_undef.cpp
@@ -15,13 +15,14 @@
 #include "source/fuzz/transformation_add_global_undef.h"
 
 #include "source/fuzz/fuzzer_util.h"
+#include "source/opt/reflect.h"
 
 namespace spvtools {
 namespace fuzz {
 
 TransformationAddGlobalUndef::TransformationAddGlobalUndef(
-    const spvtools::fuzz::protobufs::TransformationAddGlobalUndef& message)
-    : message_(message) {}
+    spvtools::fuzz::protobufs::TransformationAddGlobalUndef message)
+    : message_(std::move(message)) {}
 
 TransformationAddGlobalUndef::TransformationAddGlobalUndef(uint32_t fresh_id,
                                                            uint32_t type_id) {
@@ -35,21 +36,23 @@
   if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
-  auto type = ir_context->get_type_mgr()->GetType(message_.type_id());
-  // The type must exist, and must not be a function type.
-  return type && !type->AsFunction();
+  auto type = ir_context->get_def_use_mgr()->GetDef(message_.type_id());
+  // The type must exist, and must not be a function or pointer type.
+  return type != nullptr && opt::IsTypeInst(type->opcode()) &&
+         type->opcode() != SpvOpTypeFunction &&
+         type->opcode() != SpvOpTypePointer;
 }
 
 void TransformationAddGlobalUndef::Apply(
     opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
-  ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+  auto new_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpUndef, message_.type_id(), message_.fresh_id(),
-      opt::Instruction::OperandList()));
+      opt::Instruction::OperandList());
+  auto new_instruction_ptr = new_instruction.get();
+  ir_context->module()->AddGlobalValue(std::move(new_instruction));
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  // Inform the def-use manager about the new instruction.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(new_instruction_ptr);
 }
 
 protobufs::Transformation TransformationAddGlobalUndef::ToMessage() const {
diff --git a/source/fuzz/transformation_add_global_undef.h b/source/fuzz/transformation_add_global_undef.h
index 717dc9a..fff1ad9 100644
--- a/source/fuzz/transformation_add_global_undef.h
+++ b/source/fuzz/transformation_add_global_undef.h
@@ -26,12 +26,12 @@
 class TransformationAddGlobalUndef : public Transformation {
  public:
   explicit TransformationAddGlobalUndef(
-      const protobufs::TransformationAddGlobalUndef& message);
+      protobufs::TransformationAddGlobalUndef message);
 
   TransformationAddGlobalUndef(uint32_t fresh_id, uint32_t type_id);
 
   // - |message_.fresh_id| must be fresh
-  // - |message_.type_id| must be the id of a non-function type
+  // - |message_.type_id| must be the id of a non-function, non-pointer type
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
diff --git a/source/fuzz/transformation_add_global_variable.cpp b/source/fuzz/transformation_add_global_variable.cpp
index dd04e48..814d01b 100644
--- a/source/fuzz/transformation_add_global_variable.cpp
+++ b/source/fuzz/transformation_add_global_variable.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddGlobalVariable::TransformationAddGlobalVariable(
-    const spvtools::fuzz::protobufs::TransformationAddGlobalVariable& message)
-    : message_(message) {}
+    spvtools::fuzz::protobufs::TransformationAddGlobalVariable message)
+    : message_(std::move(message)) {}
 
 TransformationAddGlobalVariable::TransformationAddGlobalVariable(
     uint32_t fresh_id, uint32_t type_id, SpvStorageClass storage_class,
@@ -93,15 +93,13 @@
 void TransformationAddGlobalVariable::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  fuzzerutil::AddGlobalVariable(
+  opt::Instruction* new_instruction = fuzzerutil::AddGlobalVariable(
       ir_context, message_.fresh_id(), message_.type_id(),
       static_cast<SpvStorageClass>(message_.storage_class()),
       message_.initializer_id());
 
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  // Inform the def-use manager about the new instruction.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction);
 
   if (message_.value_is_irrelevant()) {
     transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
diff --git a/source/fuzz/transformation_add_global_variable.h b/source/fuzz/transformation_add_global_variable.h
index 8d46edb..d74d48a 100644
--- a/source/fuzz/transformation_add_global_variable.h
+++ b/source/fuzz/transformation_add_global_variable.h
@@ -26,7 +26,7 @@
 class TransformationAddGlobalVariable : public Transformation {
  public:
   explicit TransformationAddGlobalVariable(
-      const protobufs::TransformationAddGlobalVariable& message);
+      protobufs::TransformationAddGlobalVariable message);
 
   TransformationAddGlobalVariable(uint32_t fresh_id, uint32_t type_id,
                                   SpvStorageClass storage_class,
diff --git a/source/fuzz/transformation_add_image_sample_unused_components.cpp b/source/fuzz/transformation_add_image_sample_unused_components.cpp
index ab48f0b..1ead82b 100644
--- a/source/fuzz/transformation_add_image_sample_unused_components.cpp
+++ b/source/fuzz/transformation_add_image_sample_unused_components.cpp
@@ -22,9 +22,8 @@
 
 TransformationAddImageSampleUnusedComponents::
     TransformationAddImageSampleUnusedComponents(
-        const spvtools::fuzz::protobufs::
-            TransformationAddImageSampleUnusedComponents& message)
-    : message_(message) {}
+        protobufs::TransformationAddImageSampleUnusedComponents message)
+    : message_(std::move(message)) {}
 
 TransformationAddImageSampleUnusedComponents::
     TransformationAddImageSampleUnusedComponents(
diff --git a/source/fuzz/transformation_add_image_sample_unused_components.h b/source/fuzz/transformation_add_image_sample_unused_components.h
index 7486c76..7b13f9f 100644
--- a/source/fuzz/transformation_add_image_sample_unused_components.h
+++ b/source/fuzz/transformation_add_image_sample_unused_components.h
@@ -26,7 +26,7 @@
 class TransformationAddImageSampleUnusedComponents : public Transformation {
  public:
   explicit TransformationAddImageSampleUnusedComponents(
-      const protobufs::TransformationAddImageSampleUnusedComponents& message);
+      protobufs::TransformationAddImageSampleUnusedComponents message);
 
   TransformationAddImageSampleUnusedComponents(
       uint32_t coordinate_with_unused_components_id,
diff --git a/source/fuzz/transformation_add_local_variable.cpp b/source/fuzz/transformation_add_local_variable.cpp
index 0a7a3da..21768d2 100644
--- a/source/fuzz/transformation_add_local_variable.cpp
+++ b/source/fuzz/transformation_add_local_variable.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddLocalVariable::TransformationAddLocalVariable(
-    const spvtools::fuzz::protobufs::TransformationAddLocalVariable& message)
-    : message_(message) {}
+    spvtools::fuzz::protobufs::TransformationAddLocalVariable message)
+    : message_(std::move(message)) {}
 
 TransformationAddLocalVariable::TransformationAddLocalVariable(
     uint32_t fresh_id, uint32_t type_id, uint32_t function_id,
@@ -70,11 +70,17 @@
 void TransformationAddLocalVariable::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  fuzzerutil::AddLocalVariable(ir_context, message_.fresh_id(),
-                               message_.type_id(), message_.function_id(),
-                               message_.initializer_id());
+  opt::Instruction* new_instruction = fuzzerutil::AddLocalVariable(
+      ir_context, message_.fresh_id(), message_.type_id(),
+      message_.function_id(), message_.initializer_id());
 
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  // Inform the def-use manager about the new instruction.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction);
+  ir_context->set_instr_block(
+      new_instruction,
+      fuzzerutil::FindFunction(ir_context, message_.function_id())
+          ->entry()
+          .get());
 
   if (message_.value_is_irrelevant()) {
     transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
diff --git a/source/fuzz/transformation_add_local_variable.h b/source/fuzz/transformation_add_local_variable.h
index 963079f..b008a1c 100644
--- a/source/fuzz/transformation_add_local_variable.h
+++ b/source/fuzz/transformation_add_local_variable.h
@@ -26,7 +26,7 @@
 class TransformationAddLocalVariable : public Transformation {
  public:
   explicit TransformationAddLocalVariable(
-      const protobufs::TransformationAddLocalVariable& message);
+      protobufs::TransformationAddLocalVariable message);
 
   TransformationAddLocalVariable(uint32_t fresh_id, uint32_t type_id,
                                  uint32_t function_id, uint32_t initializer_id,
diff --git a/source/fuzz/transformation_add_loop_preheader.cpp b/source/fuzz/transformation_add_loop_preheader.cpp
index 3d50fa9..71ab18d 100644
--- a/source/fuzz/transformation_add_loop_preheader.cpp
+++ b/source/fuzz/transformation_add_loop_preheader.cpp
@@ -20,8 +20,8 @@
 namespace spvtools {
 namespace fuzz {
 TransformationAddLoopPreheader::TransformationAddLoopPreheader(
-    const protobufs::TransformationAddLoopPreheader& message)
-    : message_(message) {}
+    protobufs::TransformationAddLoopPreheader message)
+    : message_(std::move(message)) {}
 
 TransformationAddLoopPreheader::TransformationAddLoopPreheader(
     uint32_t loop_header_block, uint32_t fresh_id,
diff --git a/source/fuzz/transformation_add_loop_preheader.h b/source/fuzz/transformation_add_loop_preheader.h
index 05448f3..9d2c565 100644
--- a/source/fuzz/transformation_add_loop_preheader.h
+++ b/source/fuzz/transformation_add_loop_preheader.h
@@ -23,7 +23,7 @@
 class TransformationAddLoopPreheader : public Transformation {
  public:
   explicit TransformationAddLoopPreheader(
-      const protobufs::TransformationAddLoopPreheader& message);
+      protobufs::TransformationAddLoopPreheader message);
 
   TransformationAddLoopPreheader(uint32_t loop_header_block, uint32_t fresh_id,
                                  std::vector<uint32_t> phi_id);
diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
index 45d3fc8..657fafa 100644
--- a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
@@ -23,9 +23,8 @@
 
 TransformationAddLoopToCreateIntConstantSynonym::
     TransformationAddLoopToCreateIntConstantSynonym(
-        const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
-            message)
-    : message_(message) {}
+        protobufs::TransformationAddLoopToCreateIntConstantSynonym message)
+    : message_(std::move(message)) {}
 
 TransformationAddLoopToCreateIntConstantSynonym::
     TransformationAddLoopToCreateIntConstantSynonym(
diff --git a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
index 67c3bcd..a6dfe63 100644
--- a/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
@@ -22,8 +22,7 @@
 class TransformationAddLoopToCreateIntConstantSynonym : public Transformation {
  public:
   explicit TransformationAddLoopToCreateIntConstantSynonym(
-      const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
-          message);
+      protobufs::TransformationAddLoopToCreateIntConstantSynonym message);
 
   TransformationAddLoopToCreateIntConstantSynonym(
       uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id,
diff --git a/source/fuzz/transformation_add_no_contraction_decoration.cpp b/source/fuzz/transformation_add_no_contraction_decoration.cpp
index 29a871d..992a216 100644
--- a/source/fuzz/transformation_add_no_contraction_decoration.cpp
+++ b/source/fuzz/transformation_add_no_contraction_decoration.cpp
@@ -21,9 +21,8 @@
 
 TransformationAddNoContractionDecoration::
     TransformationAddNoContractionDecoration(
-        const spvtools::fuzz::protobufs::
-            TransformationAddNoContractionDecoration& message)
-    : message_(message) {}
+        protobufs::TransformationAddNoContractionDecoration message)
+    : message_(std::move(message)) {}
 
 TransformationAddNoContractionDecoration::
     TransformationAddNoContractionDecoration(uint32_t result_id) {
diff --git a/source/fuzz/transformation_add_no_contraction_decoration.h b/source/fuzz/transformation_add_no_contraction_decoration.h
index f5a34e8..2f78d42 100644
--- a/source/fuzz/transformation_add_no_contraction_decoration.h
+++ b/source/fuzz/transformation_add_no_contraction_decoration.h
@@ -26,7 +26,7 @@
 class TransformationAddNoContractionDecoration : public Transformation {
  public:
   explicit TransformationAddNoContractionDecoration(
-      const protobufs::TransformationAddNoContractionDecoration& message);
+      protobufs::TransformationAddNoContractionDecoration message);
 
   explicit TransformationAddNoContractionDecoration(uint32_t fresh_id);
 
diff --git a/source/fuzz/transformation_add_opphi_synonym.cpp b/source/fuzz/transformation_add_opphi_synonym.cpp
index 227c433..3c4698a 100644
--- a/source/fuzz/transformation_add_opphi_synonym.cpp
+++ b/source/fuzz/transformation_add_opphi_synonym.cpp
@@ -19,8 +19,8 @@
 namespace spvtools {
 namespace fuzz {
 TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
-    const protobufs::TransformationAddOpPhiSynonym& message)
-    : message_(message) {}
+    protobufs::TransformationAddOpPhiSynonym message)
+    : message_(std::move(message)) {}
 
 TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
     uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
diff --git a/source/fuzz/transformation_add_opphi_synonym.h b/source/fuzz/transformation_add_opphi_synonym.h
index 3b68abe..39ebea8 100644
--- a/source/fuzz/transformation_add_opphi_synonym.h
+++ b/source/fuzz/transformation_add_opphi_synonym.h
@@ -22,7 +22,7 @@
 class TransformationAddOpPhiSynonym : public Transformation {
  public:
   explicit TransformationAddOpPhiSynonym(
-      const protobufs::TransformationAddOpPhiSynonym& message);
+      protobufs::TransformationAddOpPhiSynonym message);
 
   TransformationAddOpPhiSynonym(
       uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
diff --git a/source/fuzz/transformation_add_parameter.cpp b/source/fuzz/transformation_add_parameter.cpp
index 9ed0bfb..48de3e8 100644
--- a/source/fuzz/transformation_add_parameter.cpp
+++ b/source/fuzz/transformation_add_parameter.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddParameter::TransformationAddParameter(
-    const protobufs::TransformationAddParameter& message)
-    : message_(message) {}
+    protobufs::TransformationAddParameter message)
+    : message_(std::move(message)) {}
 
 TransformationAddParameter::TransformationAddParameter(
     uint32_t function_id, uint32_t parameter_fresh_id,
diff --git a/source/fuzz/transformation_add_parameter.h b/source/fuzz/transformation_add_parameter.h
index a33521d..0bc096e 100644
--- a/source/fuzz/transformation_add_parameter.h
+++ b/source/fuzz/transformation_add_parameter.h
@@ -26,7 +26,7 @@
 class TransformationAddParameter : public Transformation {
  public:
   explicit TransformationAddParameter(
-      const protobufs::TransformationAddParameter& message);
+      protobufs::TransformationAddParameter message);
 
   TransformationAddParameter(uint32_t function_id, uint32_t parameter_fresh_id,
                              uint32_t parameter_type_id,
diff --git a/source/fuzz/transformation_add_relaxed_decoration.cpp b/source/fuzz/transformation_add_relaxed_decoration.cpp
index 7b51305..b66a1a8 100644
--- a/source/fuzz/transformation_add_relaxed_decoration.cpp
+++ b/source/fuzz/transformation_add_relaxed_decoration.cpp
@@ -20,9 +20,8 @@
 namespace fuzz {
 
 TransformationAddRelaxedDecoration::TransformationAddRelaxedDecoration(
-    const spvtools::fuzz::protobufs::TransformationAddRelaxedDecoration&
-        message)
-    : message_(message) {}
+    protobufs::TransformationAddRelaxedDecoration message)
+    : message_(std::move(message)) {}
 
 TransformationAddRelaxedDecoration::TransformationAddRelaxedDecoration(
     uint32_t result_id) {
diff --git a/source/fuzz/transformation_add_relaxed_decoration.h b/source/fuzz/transformation_add_relaxed_decoration.h
index 3f8bf3e..c016349 100644
--- a/source/fuzz/transformation_add_relaxed_decoration.h
+++ b/source/fuzz/transformation_add_relaxed_decoration.h
@@ -26,7 +26,7 @@
 class TransformationAddRelaxedDecoration : public Transformation {
  public:
   explicit TransformationAddRelaxedDecoration(
-      const protobufs::TransformationAddRelaxedDecoration& message);
+      protobufs::TransformationAddRelaxedDecoration message);
 
   explicit TransformationAddRelaxedDecoration(uint32_t fresh_id);
 
diff --git a/source/fuzz/transformation_add_synonym.cpp b/source/fuzz/transformation_add_synonym.cpp
index a516916..69269e5 100644
--- a/source/fuzz/transformation_add_synonym.cpp
+++ b/source/fuzz/transformation_add_synonym.cpp
@@ -102,14 +102,19 @@
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
   // Add a synonymous instruction.
-  FindInstruction(message_.insert_before(), ir_context)
-      ->InsertBefore(
-          MakeSynonymousInstruction(ir_context, *transformation_context));
+  auto new_instruction =
+      MakeSynonymousInstruction(ir_context, *transformation_context);
+  auto new_instruction_ptr = new_instruction.get();
+  auto insert_before = FindInstruction(message_.insert_before(), ir_context);
+  insert_before->InsertBefore(std::move(new_instruction));
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.synonym_fresh_id());
 
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  // Inform the def-use manager about the new instruction and record its basic
+  // block.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+  ir_context->set_instr_block(new_instruction_ptr,
+                              ir_context->get_instr_block(insert_before));
 
   // Propagate PointeeValueIsIrrelevant fact.
   const auto* new_synonym_type = ir_context->get_type_mgr()->GetType(
@@ -146,7 +151,8 @@
     return false;
   }
 
-  if (!fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context, inst)) {
+  if (!fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
+                                    *inst)) {
     return false;
   }
 
@@ -165,6 +171,18 @@
 
       return type->AsInteger() || type->AsFloat();
     }
+    case protobufs::TransformationAddSynonym::BITWISE_OR:
+    case protobufs::TransformationAddSynonym::BITWISE_XOR: {
+      // The instruction must be either an integer or a vector of integers.
+      const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
+      assert(type && "Instruction's result id is invalid");
+
+      if (const auto* vector = type->AsVector()) {
+        return vector->element_type()->AsInteger();
+      }
+
+      return type->AsInteger();
+    }
     case protobufs::TransformationAddSynonym::COPY_OBJECT:
       // All checks for OpCopyObject are handled by
       // fuzzerutil::CanMakeSynonymOf.
@@ -190,68 +208,56 @@
   auto synonym_type_id =
       fuzzerutil::GetTypeId(ir_context, message_.result_id());
   assert(synonym_type_id && "Synonym has invalid type id");
+  auto opcode = SpvOpNop;
+  const auto* synonym_type =
+      ir_context->get_type_mgr()->GetType(synonym_type_id);
+  assert(synonym_type && "Synonym has invalid type");
+
+  auto is_integral = (synonym_type->AsVector() &&
+                      synonym_type->AsVector()->element_type()->AsInteger()) ||
+                     synonym_type->AsInteger();
 
   switch (message_.synonym_type()) {
     case protobufs::TransformationAddSynonym::SUB_ZERO:
+      opcode = is_integral ? SpvOpISub : SpvOpFSub;
+      break;
     case protobufs::TransformationAddSynonym::MUL_ONE:
-    case protobufs::TransformationAddSynonym::ADD_ZERO: {
-      const auto* synonym_type =
-          ir_context->get_type_mgr()->GetType(synonym_type_id);
-      assert(synonym_type && "Synonym has invalid type");
+      opcode = is_integral ? SpvOpIMul : SpvOpFMul;
+      break;
+    case protobufs::TransformationAddSynonym::ADD_ZERO:
+      opcode = is_integral ? SpvOpIAdd : SpvOpFAdd;
+      break;
+    case protobufs::TransformationAddSynonym::LOGICAL_OR:
+      opcode = SpvOpLogicalOr;
+      break;
+    case protobufs::TransformationAddSynonym::LOGICAL_AND:
+      opcode = SpvOpLogicalAnd;
+      break;
+    case protobufs::TransformationAddSynonym::BITWISE_OR:
+      opcode = SpvOpBitwiseOr;
+      break;
+    case protobufs::TransformationAddSynonym::BITWISE_XOR:
+      opcode = SpvOpBitwiseXor;
+      break;
 
-      // Compute instruction's opcode based on the type of the operand.
-      // We have already checked that the operand is either a scalar or a vector
-      // of either integers or floats.
-      auto is_integral =
-          (synonym_type->AsVector() &&
-           synonym_type->AsVector()->element_type()->AsInteger()) ||
-          synonym_type->AsInteger();
-      auto opcode = SpvOpNop;
-      switch (message_.synonym_type()) {
-        case protobufs::TransformationAddSynonym::SUB_ZERO:
-          opcode = is_integral ? SpvOpISub : SpvOpFSub;
-          break;
-        case protobufs::TransformationAddSynonym::MUL_ONE:
-          opcode = is_integral ? SpvOpIMul : SpvOpFMul;
-          break;
-        case protobufs::TransformationAddSynonym::ADD_ZERO:
-          opcode = is_integral ? SpvOpIAdd : SpvOpFAdd;
-          break;
-        default:
-          assert(false && "Unreachable");
-          break;
-      }
-
-      return MakeUnique<opt::Instruction>(
-          ir_context, opcode, synonym_type_id, message_.synonym_fresh_id(),
-          opt::Instruction::OperandList{
-              {SPV_OPERAND_TYPE_ID, {message_.result_id()}},
-              {SPV_OPERAND_TYPE_ID,
-               {MaybeGetConstantId(ir_context, transformation_context)}}});
-    }
     case protobufs::TransformationAddSynonym::COPY_OBJECT:
       return MakeUnique<opt::Instruction>(
           ir_context, SpvOpCopyObject, synonym_type_id,
           message_.synonym_fresh_id(),
           opt::Instruction::OperandList{
               {SPV_OPERAND_TYPE_ID, {message_.result_id()}}});
-    case protobufs::TransformationAddSynonym::LOGICAL_OR:
-    case protobufs::TransformationAddSynonym::LOGICAL_AND: {
-      auto opcode = message_.synonym_type() ==
-                            protobufs::TransformationAddSynonym::LOGICAL_OR
-                        ? SpvOpLogicalOr
-                        : SpvOpLogicalAnd;
-      return MakeUnique<opt::Instruction>(
-          ir_context, opcode, synonym_type_id, message_.synonym_fresh_id(),
-          opt::Instruction::OperandList{
-              {SPV_OPERAND_TYPE_ID, {message_.result_id()}},
-              {SPV_OPERAND_TYPE_ID,
-               {MaybeGetConstantId(ir_context, transformation_context)}}});
-    }
+
     default:
       assert(false && "Unhandled synonym type");
       return nullptr;
   }
+
+  return MakeUnique<opt::Instruction>(
+      ir_context, opcode, synonym_type_id, message_.synonym_fresh_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.result_id()}},
+          {SPV_OPERAND_TYPE_ID,
+           {MaybeGetConstantId(ir_context, transformation_context)}}});
 }
 
 uint32_t TransformationAddSynonym::MaybeGetConstantId(
@@ -268,6 +274,8 @@
     case protobufs::TransformationAddSynonym::ADD_ZERO:
     case protobufs::TransformationAddSynonym::SUB_ZERO:
     case protobufs::TransformationAddSynonym::LOGICAL_OR:
+    case protobufs::TransformationAddSynonym::BITWISE_OR:
+    case protobufs::TransformationAddSynonym::BITWISE_XOR:
       return fuzzerutil::MaybeGetZeroConstant(
           ir_context, transformation_context, synonym_type_id, false);
     case protobufs::TransformationAddSynonym::MUL_ONE:
@@ -314,6 +322,8 @@
     case protobufs::TransformationAddSynonym::LOGICAL_OR:
     case protobufs::TransformationAddSynonym::MUL_ONE:
     case protobufs::TransformationAddSynonym::LOGICAL_AND:
+    case protobufs::TransformationAddSynonym::BITWISE_OR:
+    case protobufs::TransformationAddSynonym::BITWISE_XOR:
       return true;
     default:
       return false;
diff --git a/source/fuzz/transformation_add_type_array.cpp b/source/fuzz/transformation_add_type_array.cpp
index c9f6a87..45bc8df 100644
--- a/source/fuzz/transformation_add_type_array.cpp
+++ b/source/fuzz/transformation_add_type_array.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddTypeArray::TransformationAddTypeArray(
-    const spvtools::fuzz::protobufs::TransformationAddTypeArray& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeArray message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypeArray::TransformationAddTypeArray(uint32_t fresh_id,
                                                        uint32_t element_type_id,
@@ -39,9 +39,11 @@
   }
   auto element_type =
       ir_context->get_type_mgr()->GetType(message_.element_type_id());
-  if (!element_type || element_type->AsFunction()) {
-    // The element type id either does not refer to a type, or refers to a
-    // function type; both are illegal.
+  if (!element_type || element_type->AsFunction() ||
+      fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context,
+                                                  message_.element_type_id())) {
+    // The element type id either does not refer to a type, refers to a function
+    // type, or refers to a block-decorated struct. These cases are all illegal.
     return false;
   }
   auto constant =
@@ -69,13 +71,17 @@
   opt::Instruction::OperandList in_operands;
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.element_type_id()}});
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.size_id()}});
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeArray, 0, message_.fresh_id(), in_operands));
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeArray, 0, message_.fresh_id(), in_operands);
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->module()->AddType(std::move(type_instruction));
+
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Inform the def use manager that there is a new definition. Invalidate the
+  // type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypeArray::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_array.h b/source/fuzz/transformation_add_type_array.h
index ebefc23..3162497 100644
--- a/source/fuzz/transformation_add_type_array.h
+++ b/source/fuzz/transformation_add_type_array.h
@@ -26,15 +26,17 @@
 class TransformationAddTypeArray : public Transformation {
  public:
   explicit TransformationAddTypeArray(
-      const protobufs::TransformationAddTypeArray& message);
+      protobufs::TransformationAddTypeArray message);
 
   TransformationAddTypeArray(uint32_t fresh_id, uint32_t element_type_id,
                              uint32_t size_id);
 
   // - |message_.fresh_id| must be fresh
   // - |message_.element_type_id| must be the id of a non-function type
+  // - |message_.member_type_id| must not be the result id of an OpTypeStruct
+  //   instruction that has the Block or BufferBlock decoration
   // - |message_.size_id| must be the id of a 32-bit integer constant that is
-  //   positive when interpreted as signed.
+  //   positive when interpreted as signed
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
diff --git a/source/fuzz/transformation_add_type_boolean.cpp b/source/fuzz/transformation_add_type_boolean.cpp
index ebbfabc..30ff43e 100644
--- a/source/fuzz/transformation_add_type_boolean.cpp
+++ b/source/fuzz/transformation_add_type_boolean.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddTypeBoolean::TransformationAddTypeBoolean(
-    const spvtools::fuzz::protobufs::TransformationAddTypeBoolean& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeBoolean message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypeBoolean::TransformationAddTypeBoolean(uint32_t fresh_id) {
   message_.set_fresh_id(fresh_id);
@@ -42,13 +42,17 @@
 void TransformationAddTypeBoolean::Apply(
     opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   opt::Instruction::OperandList empty_operands;
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeBool, 0, message_.fresh_id(), empty_operands));
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeBool, 0, message_.fresh_id(), empty_operands);
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->module()->AddType(std::move(type_instruction));
+
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Inform the def use manager that there is a new definition. Invalidate the
+  // type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypeBoolean::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_boolean.h b/source/fuzz/transformation_add_type_boolean.h
index 25c9272..ee64015 100644
--- a/source/fuzz/transformation_add_type_boolean.h
+++ b/source/fuzz/transformation_add_type_boolean.h
@@ -25,7 +25,7 @@
 class TransformationAddTypeBoolean : public Transformation {
  public:
   explicit TransformationAddTypeBoolean(
-      const protobufs::TransformationAddTypeBoolean& message);
+      protobufs::TransformationAddTypeBoolean message);
 
   explicit TransformationAddTypeBoolean(uint32_t fresh_id);
 
diff --git a/source/fuzz/transformation_add_type_float.cpp b/source/fuzz/transformation_add_type_float.cpp
index da3f3e4..1b88b25 100644
--- a/source/fuzz/transformation_add_type_float.cpp
+++ b/source/fuzz/transformation_add_type_float.cpp
@@ -26,8 +26,8 @@
 }
 
 TransformationAddTypeFloat::TransformationAddTypeFloat(
-    const spvtools::fuzz::protobufs::TransformationAddTypeFloat& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeFloat message)
+    : message_(std::move(message)) {}
 
 bool TransformationAddTypeFloat::IsApplicable(
     opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
@@ -65,11 +65,18 @@
 
 void TransformationAddTypeFloat::Apply(
     opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
-  fuzzerutil::AddFloatType(ir_context, message_.fresh_id(), message_.width());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeFloat, 0, message_.fresh_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.width()}}});
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->module()->AddType(std::move(type_instruction));
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Inform the def use manager that there is a new definition, and invalidate
+  // the type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypeFloat::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_float.h b/source/fuzz/transformation_add_type_float.h
index 30cd0fc..e049d9a 100644
--- a/source/fuzz/transformation_add_type_float.h
+++ b/source/fuzz/transformation_add_type_float.h
@@ -26,7 +26,7 @@
 class TransformationAddTypeFloat : public Transformation {
  public:
   explicit TransformationAddTypeFloat(
-      const protobufs::TransformationAddTypeFloat& message);
+      protobufs::TransformationAddTypeFloat message);
 
   TransformationAddTypeFloat(uint32_t fresh_id, uint32_t width);
 
diff --git a/source/fuzz/transformation_add_type_function.cpp b/source/fuzz/transformation_add_type_function.cpp
index b6cddfc..e96067f 100644
--- a/source/fuzz/transformation_add_type_function.cpp
+++ b/source/fuzz/transformation_add_type_function.cpp
@@ -22,8 +22,8 @@
 namespace fuzz {
 
 TransformationAddTypeFunction::TransformationAddTypeFunction(
-    const spvtools::fuzz::protobufs::TransformationAddTypeFunction& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeFunction message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypeFunction::TransformationAddTypeFunction(
     uint32_t fresh_id, uint32_t return_type_id,
diff --git a/source/fuzz/transformation_add_type_function.h b/source/fuzz/transformation_add_type_function.h
index 59ded2b..7104457 100644
--- a/source/fuzz/transformation_add_type_function.h
+++ b/source/fuzz/transformation_add_type_function.h
@@ -28,7 +28,7 @@
 class TransformationAddTypeFunction : public Transformation {
  public:
   explicit TransformationAddTypeFunction(
-      const protobufs::TransformationAddTypeFunction& message);
+      protobufs::TransformationAddTypeFunction message);
 
   TransformationAddTypeFunction(uint32_t fresh_id, uint32_t return_type_id,
                                 const std::vector<uint32_t>& argument_type_ids);
diff --git a/source/fuzz/transformation_add_type_int.cpp b/source/fuzz/transformation_add_type_int.cpp
index 253ea15..d4ef981 100644
--- a/source/fuzz/transformation_add_type_int.cpp
+++ b/source/fuzz/transformation_add_type_int.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddTypeInt::TransformationAddTypeInt(
-    const spvtools::fuzz::protobufs::TransformationAddTypeInt& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeInt message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypeInt::TransformationAddTypeInt(uint32_t fresh_id,
                                                    uint32_t width,
@@ -74,12 +74,21 @@
 
 void TransformationAddTypeInt::Apply(opt::IRContext* ir_context,
                                      TransformationContext* /*unused*/) const {
-  fuzzerutil::AddIntegerType(ir_context, message_.fresh_id(), message_.width(),
-                             message_.is_signed());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeInt, 0, message_.fresh_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.width()}},
+          {SPV_OPERAND_TYPE_LITERAL_INTEGER,
+           {message_.is_signed() ? 1u : 0u}}});
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->module()->AddType(std::move(type_instruction));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Inform the def use manager that there is a new definition. Invalidate the
+  // type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypeInt::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_int.h b/source/fuzz/transformation_add_type_int.h
index 20c90ca..dc67b7d 100644
--- a/source/fuzz/transformation_add_type_int.h
+++ b/source/fuzz/transformation_add_type_int.h
@@ -26,7 +26,7 @@
 class TransformationAddTypeInt : public Transformation {
  public:
   explicit TransformationAddTypeInt(
-      const protobufs::TransformationAddTypeInt& message);
+      protobufs::TransformationAddTypeInt message);
 
   TransformationAddTypeInt(uint32_t fresh_id, uint32_t width, bool is_signed);
 
diff --git a/source/fuzz/transformation_add_type_matrix.cpp b/source/fuzz/transformation_add_type_matrix.cpp
index cecebb4..b574b01 100644
--- a/source/fuzz/transformation_add_type_matrix.cpp
+++ b/source/fuzz/transformation_add_type_matrix.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddTypeMatrix::TransformationAddTypeMatrix(
-    const spvtools::fuzz::protobufs::TransformationAddTypeMatrix& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeMatrix message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypeMatrix::TransformationAddTypeMatrix(
     uint32_t fresh_id, uint32_t column_type_id, uint32_t column_count) {
@@ -52,13 +52,17 @@
   in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.column_type_id()}});
   in_operands.push_back(
       {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.column_count()}});
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeMatrix, 0, message_.fresh_id(), in_operands));
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeMatrix, 0, message_.fresh_id(), in_operands);
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->module()->AddType(std::move(type_instruction));
+
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Inform the def use manager that there is a new definition. Invalidate the
+  // type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypeMatrix::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_matrix.h b/source/fuzz/transformation_add_type_matrix.h
index f1d4165..b4788b1 100644
--- a/source/fuzz/transformation_add_type_matrix.h
+++ b/source/fuzz/transformation_add_type_matrix.h
@@ -26,7 +26,7 @@
 class TransformationAddTypeMatrix : public Transformation {
  public:
   explicit TransformationAddTypeMatrix(
-      const protobufs::TransformationAddTypeMatrix& message);
+      protobufs::TransformationAddTypeMatrix message);
 
   TransformationAddTypeMatrix(uint32_t fresh_id, uint32_t column_type_id,
                               uint32_t column_count);
diff --git a/source/fuzz/transformation_add_type_pointer.cpp b/source/fuzz/transformation_add_type_pointer.cpp
index f74768d..c6c3945 100644
--- a/source/fuzz/transformation_add_type_pointer.cpp
+++ b/source/fuzz/transformation_add_type_pointer.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddTypePointer::TransformationAddTypePointer(
-    const spvtools::fuzz::protobufs::TransformationAddTypePointer& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypePointer message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypePointer::TransformationAddTypePointer(
     uint32_t fresh_id, SpvStorageClass storage_class, uint32_t base_type_id) {
@@ -47,13 +47,17 @@
   opt::Instruction::OperandList in_operands = {
       {SPV_OPERAND_TYPE_STORAGE_CLASS, {message_.storage_class()}},
       {SPV_OPERAND_TYPE_ID, {message_.base_type_id()}}};
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypePointer, 0, message_.fresh_id(), in_operands));
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypePointer, 0, message_.fresh_id(), in_operands);
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->module()->AddType(std::move(type_instruction));
+
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Inform the def use manager that there is a new definition. Invalidate the
+  // type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypePointer::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_pointer.h b/source/fuzz/transformation_add_type_pointer.h
index 3f686e9..8468c14 100644
--- a/source/fuzz/transformation_add_type_pointer.h
+++ b/source/fuzz/transformation_add_type_pointer.h
@@ -26,7 +26,7 @@
 class TransformationAddTypePointer : public Transformation {
  public:
   explicit TransformationAddTypePointer(
-      const protobufs::TransformationAddTypePointer& message);
+      protobufs::TransformationAddTypePointer message);
 
   TransformationAddTypePointer(uint32_t fresh_id, SpvStorageClass storage_class,
                                uint32_t base_type_id);
diff --git a/source/fuzz/transformation_add_type_struct.cpp b/source/fuzz/transformation_add_type_struct.cpp
index b20ffb0..d7f0711 100644
--- a/source/fuzz/transformation_add_type_struct.cpp
+++ b/source/fuzz/transformation_add_type_struct.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddTypeStruct::TransformationAddTypeStruct(
-    const spvtools::fuzz::protobufs::TransformationAddTypeStruct& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeStruct message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypeStruct::TransformationAddTypeStruct(
     uint32_t fresh_id, const std::vector<uint32_t>& member_type_ids) {
@@ -39,9 +39,11 @@
   }
   for (auto member_type : message_.member_type_id()) {
     auto type = ir_context->get_type_mgr()->GetType(member_type);
-    if (!type || type->AsFunction()) {
-      // The member type id either does not refer to a type, or refers to a
-      // function type; both are illegal.
+    if (!type || type->AsFunction() ||
+        fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context, member_type)) {
+      // The member type id either does not refer to a type, refers to a
+      // function type, or refers to a block-decorated struct. These cases are
+      // all illegal.
       return false;
     }
 
@@ -58,14 +60,36 @@
 
 void TransformationAddTypeStruct::Apply(
     opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
-  fuzzerutil::AddStructType(
-      ir_context, message_.fresh_id(),
-      std::vector<uint32_t>(message_.member_type_id().begin(),
-                            message_.member_type_id().end()));
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  opt::Instruction::OperandList operands;
+  operands.reserve(message_.member_type_id().size());
+
+  for (auto type_id : message_.member_type_id()) {
+    const auto* type = ir_context->get_type_mgr()->GetType(type_id);
+    (void)type;  // Make compiler happy in release mode.
+    assert(type && !type->AsFunction() && "Component's type id is invalid");
+
+    if (type->AsStruct()) {
+      // From the spec for the BuiltIn decoration:
+      // - When applied to a structure-type member, that structure type cannot
+      //   be contained as a member of another structure type.
+      assert(!fuzzerutil::MembersHaveBuiltInDecoration(ir_context, type_id) &&
+             "A member struct has BuiltIn members");
+    }
+
+    operands.push_back({SPV_OPERAND_TYPE_ID, {type_id}});
+  }
+
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeStruct, 0, message_.fresh_id(), std::move(operands));
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->AddType(std::move(type_instruction));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Inform the def use manager that there is a new definition. Invalidate the
+  // type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypeStruct::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_struct.h b/source/fuzz/transformation_add_type_struct.h
index 94be42a..6a8dce7 100644
--- a/source/fuzz/transformation_add_type_struct.h
+++ b/source/fuzz/transformation_add_type_struct.h
@@ -28,7 +28,7 @@
 class TransformationAddTypeStruct : public Transformation {
  public:
   explicit TransformationAddTypeStruct(
-      const protobufs::TransformationAddTypeStruct& message);
+      protobufs::TransformationAddTypeStruct message);
 
   TransformationAddTypeStruct(uint32_t fresh_id,
                               const std::vector<uint32_t>& component_type_ids);
@@ -37,7 +37,9 @@
   // - |message_.member_type_id| must be a sequence of non-function type ids
   // - |message_.member_type_id| may not contain a result id of an OpTypeStruct
   //   instruction with BuiltIn members (i.e. members of the struct are
-  //   decorated via OpMemberDecorate with BuiltIn decoration).
+  //   decorated via OpMemberDecorate with BuiltIn decoration)
+  // - |message_.member_type_id| may not contain a result id of an OpTypeStruct
+  //   instruction that has the Block or BufferBlock decoration
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
diff --git a/source/fuzz/transformation_add_type_vector.cpp b/source/fuzz/transformation_add_type_vector.cpp
index a3b0010..4da0ff0 100644
--- a/source/fuzz/transformation_add_type_vector.cpp
+++ b/source/fuzz/transformation_add_type_vector.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationAddTypeVector::TransformationAddTypeVector(
-    const spvtools::fuzz::protobufs::TransformationAddTypeVector& message)
-    : message_(message) {}
+    protobufs::TransformationAddTypeVector message)
+    : message_(std::move(message)) {}
 
 TransformationAddTypeVector::TransformationAddTypeVector(
     uint32_t fresh_id, uint32_t component_type_id, uint32_t component_count) {
@@ -46,13 +46,30 @@
 
 void TransformationAddTypeVector::Apply(
     opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
-  fuzzerutil::AddVectorType(ir_context, message_.fresh_id(),
-                            message_.component_type_id(),
-                            message_.component_count());
-  // We have added an instruction to the module, so need to be careful about the
-  // validity of existing analyses.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  const auto* component_type =
+      ir_context->get_type_mgr()->GetType(message_.component_type_id());
+  (void)component_type;  // Make compiler happy in release mode.
+  assert(component_type &&
+         (component_type->AsInteger() || component_type->AsFloat() ||
+          component_type->AsBool()) &&
+         "|component_type_id| is invalid");
+  assert(message_.component_count() >= 2 && message_.component_count() <= 4 &&
+         "Precondition: component count must be in range [2, 4].");
+
+  auto type_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeVector, 0, message_.fresh_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.component_type_id()}},
+          {SPV_OPERAND_TYPE_LITERAL_INTEGER, {message_.component_count()}}});
+  auto type_instruction_ptr = type_instruction.get();
+  ir_context->module()->AddType(std::move(type_instruction));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Inform the def use manager that there is a new definition. Invalidate the
+  // type manager since we have added a new type.
+  ir_context->get_def_use_mgr()->AnalyzeInstDef(type_instruction_ptr);
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisTypes);
 }
 
 protobufs::Transformation TransformationAddTypeVector::ToMessage() const {
diff --git a/source/fuzz/transformation_add_type_vector.h b/source/fuzz/transformation_add_type_vector.h
index c25d565..43460ce 100644
--- a/source/fuzz/transformation_add_type_vector.h
+++ b/source/fuzz/transformation_add_type_vector.h
@@ -26,7 +26,7 @@
 class TransformationAddTypeVector : public Transformation {
  public:
   explicit TransformationAddTypeVector(
-      const protobufs::TransformationAddTypeVector& message);
+      protobufs::TransformationAddTypeVector message);
 
   TransformationAddTypeVector(uint32_t fresh_id, uint32_t component_type_id,
                               uint32_t component_count);
diff --git a/source/fuzz/transformation_adjust_branch_weights.cpp b/source/fuzz/transformation_adjust_branch_weights.cpp
index 8b74ed3..21fef25 100644
--- a/source/fuzz/transformation_adjust_branch_weights.cpp
+++ b/source/fuzz/transformation_adjust_branch_weights.cpp
@@ -28,8 +28,8 @@
 }  // namespace
 
 TransformationAdjustBranchWeights::TransformationAdjustBranchWeights(
-    const spvtools::fuzz::protobufs::TransformationAdjustBranchWeights& message)
-    : message_(message) {}
+    protobufs::TransformationAdjustBranchWeights message)
+    : message_(std::move(message)) {}
 
 TransformationAdjustBranchWeights::TransformationAdjustBranchWeights(
     const protobufs::InstructionDescriptor& instruction_descriptor,
diff --git a/source/fuzz/transformation_adjust_branch_weights.h b/source/fuzz/transformation_adjust_branch_weights.h
index 4d451a5..41eceeb 100644
--- a/source/fuzz/transformation_adjust_branch_weights.h
+++ b/source/fuzz/transformation_adjust_branch_weights.h
@@ -26,7 +26,7 @@
 class TransformationAdjustBranchWeights : public Transformation {
  public:
   explicit TransformationAdjustBranchWeights(
-      const protobufs::TransformationAdjustBranchWeights& message);
+      protobufs::TransformationAdjustBranchWeights message);
 
   TransformationAdjustBranchWeights(
       const protobufs::InstructionDescriptor& instruction_descriptor,
diff --git a/source/fuzz/transformation_composite_construct.cpp b/source/fuzz/transformation_composite_construct.cpp
index f0de1ae..2d8e599 100644
--- a/source/fuzz/transformation_composite_construct.cpp
+++ b/source/fuzz/transformation_composite_construct.cpp
@@ -23,8 +23,8 @@
 namespace fuzz {
 
 TransformationCompositeConstruct::TransformationCompositeConstruct(
-    const protobufs::TransformationCompositeConstruct& message)
-    : message_(message) {}
+    protobufs::TransformationCompositeConstruct message)
+    : message_(std::move(message)) {}
 
 TransformationCompositeConstruct::TransformationCompositeConstruct(
     uint32_t composite_type_id, std::vector<uint32_t> component,
@@ -120,12 +120,18 @@
   }
 
   // Insert an OpCompositeConstruct instruction.
-  insert_before.InsertBefore(MakeUnique<opt::Instruction>(
+  auto new_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpCompositeConstruct, message_.composite_type_id(),
-      message_.fresh_id(), in_operands));
+      message_.fresh_id(), in_operands);
+  auto new_instruction_ptr = new_instruction.get();
+  insert_before.InsertBefore(std::move(new_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+  ir_context->set_instr_block(new_instruction_ptr, destination_block);
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // No analyses need to be invalidated since the transformation is local to a
+  // block and the def-use and instruction-to-block mappings have been updated.
 
   AddDataSynonymFacts(ir_context, transformation_context);
 }
@@ -291,7 +297,7 @@
         composite_type->AsVector() && component_type->AsVector();
     if (!fuzzerutil::CanMakeSynonymOf(
             ir_context, *transformation_context,
-            ir_context->get_def_use_mgr()->GetDef(component))) {
+            *ir_context->get_def_use_mgr()->GetDef(component))) {
       // We can't make a synonym of this component, so we skip on to the next
       // component.  In the case where we're packing a vector into a vector we
       // have to skip as many components of the resulting vectors as there are
diff --git a/source/fuzz/transformation_composite_construct.h b/source/fuzz/transformation_composite_construct.h
index 3a3e43c..cc44a61 100644
--- a/source/fuzz/transformation_composite_construct.h
+++ b/source/fuzz/transformation_composite_construct.h
@@ -26,7 +26,7 @@
 class TransformationCompositeConstruct : public Transformation {
  public:
   explicit TransformationCompositeConstruct(
-      const protobufs::TransformationCompositeConstruct& message);
+      protobufs::TransformationCompositeConstruct message);
 
   TransformationCompositeConstruct(
       uint32_t composite_type_id, std::vector<uint32_t> component,
diff --git a/source/fuzz/transformation_composite_extract.cpp b/source/fuzz/transformation_composite_extract.cpp
index 2aff02f..0fbd4e1 100644
--- a/source/fuzz/transformation_composite_extract.cpp
+++ b/source/fuzz/transformation_composite_extract.cpp
@@ -24,8 +24,8 @@
 namespace fuzz {
 
 TransformationCompositeExtract::TransformationCompositeExtract(
-    const spvtools::fuzz::protobufs::TransformationCompositeExtract& message)
-    : message_(message) {}
+    protobufs::TransformationCompositeExtract message)
+    : message_(std::move(message)) {}
 
 TransformationCompositeExtract::TransformationCompositeExtract(
     const protobufs::InstructionDescriptor& instruction_to_insert_before,
@@ -89,15 +89,20 @@
   auto extracted_type = fuzzerutil::WalkCompositeTypeIndices(
       ir_context, composite_instruction->type_id(), message_.index());
 
-  FindInstruction(message_.instruction_to_insert_before(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  opt::Instruction* new_instruction =
+      insert_before->InsertBefore(MakeUnique<opt::Instruction>(
           ir_context, SpvOpCompositeExtract, extracted_type,
           message_.fresh_id(), extract_operands));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction);
+  ir_context->set_instr_block(new_instruction,
+                              ir_context->get_instr_block(insert_before));
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  // No analyses need to be invalidated since the transformation is local to a
+  // block and the def-use and instruction-to-block mappings have been updated.
 
   AddDataSynonymFacts(ir_context, transformation_context);
 }
@@ -120,7 +125,7 @@
   // or if the result id into which we are extracting is irrelevant.
   if (!fuzzerutil::CanMakeSynonymOf(
           ir_context, *transformation_context,
-          ir_context->get_def_use_mgr()->GetDef(message_.composite_id())) ||
+          *ir_context->get_def_use_mgr()->GetDef(message_.composite_id())) ||
       transformation_context->GetFactManager()->IdIsIrrelevant(
           message_.fresh_id())) {
     return;
diff --git a/source/fuzz/transformation_composite_extract.h b/source/fuzz/transformation_composite_extract.h
index 0f5348a..0682a61 100644
--- a/source/fuzz/transformation_composite_extract.h
+++ b/source/fuzz/transformation_composite_extract.h
@@ -26,7 +26,7 @@
 class TransformationCompositeExtract : public Transformation {
  public:
   explicit TransformationCompositeExtract(
-      const protobufs::TransformationCompositeExtract& message);
+      protobufs::TransformationCompositeExtract message);
 
   TransformationCompositeExtract(
       const protobufs::InstructionDescriptor& instruction_to_insert_before,
diff --git a/source/fuzz/transformation_composite_insert.cpp b/source/fuzz/transformation_composite_insert.cpp
index cc68141..60fa562 100644
--- a/source/fuzz/transformation_composite_insert.cpp
+++ b/source/fuzz/transformation_composite_insert.cpp
@@ -14,7 +14,6 @@
 
 #include "transformation_composite_insert.h"
 
-#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 
@@ -22,8 +21,8 @@
 namespace fuzz {
 
 TransformationCompositeInsert::TransformationCompositeInsert(
-    const spvtools::fuzz::protobufs::TransformationCompositeInsert& message)
-    : message_(message) {}
+    protobufs::TransformationCompositeInsert message)
+    : message_(std::move(message)) {}
 
 TransformationCompositeInsert::TransformationCompositeInsert(
     const protobufs::InstructionDescriptor& instruction_to_insert_before,
@@ -124,15 +123,21 @@
   auto composite_type_id =
       fuzzerutil::GetTypeId(ir_context, message_.composite_id());
 
-  FindInstruction(message_.instruction_to_insert_before(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
-          ir_context, SpvOpCompositeInsert, composite_type_id,
-          message_.fresh_id(), std::move(in_operands)));
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  auto new_instruction = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpCompositeInsert, composite_type_id, message_.fresh_id(),
+      std::move(in_operands));
+  auto new_instruction_ptr = new_instruction.get();
+  insert_before->InsertBefore(std::move(new_instruction));
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
-  // We have modified the module so most analyzes are now invalid.
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  // Inform the def-use manager about the new instruction and record its basic
+  // block.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+  ir_context->set_instr_block(new_instruction_ptr,
+                              ir_context->get_instr_block(insert_before));
 
   // Add data synonym facts that arise from the insertion.
   AddDataSynonymFacts(ir_context, transformation_context);
@@ -214,9 +219,9 @@
         continue;
       }
       current_index.push_back(i);
-      if (fuzzerutil::CanMakeSynonymOf(
-              ir_context, *transformation_context,
-              ir_context->get_def_use_mgr()->GetDef(message_.composite_id()))) {
+      if (fuzzerutil::CanMakeSynonymOf(ir_context, *transformation_context,
+                                       *ir_context->get_def_use_mgr()->GetDef(
+                                           message_.composite_id()))) {
         transformation_context->GetFactManager()->AddFactDataSynonym(
             MakeDataDescriptor(message_.fresh_id(), current_index),
             MakeDataDescriptor(message_.composite_id(), current_index));
@@ -230,7 +235,7 @@
   // synonymous with the result of the insert instruction at the given index.
   if (fuzzerutil::CanMakeSynonymOf(
           ir_context, *transformation_context,
-          ir_context->get_def_use_mgr()->GetDef(message_.object_id()))) {
+          *ir_context->get_def_use_mgr()->GetDef(message_.object_id()))) {
     transformation_context->GetFactManager()->AddFactDataSynonym(
         MakeDataDescriptor(message_.object_id(), {}),
         MakeDataDescriptor(message_.fresh_id(), index));
diff --git a/source/fuzz/transformation_composite_insert.h b/source/fuzz/transformation_composite_insert.h
index f229014..413d41f 100644
--- a/source/fuzz/transformation_composite_insert.h
+++ b/source/fuzz/transformation_composite_insert.h
@@ -26,7 +26,7 @@
 class TransformationCompositeInsert : public Transformation {
  public:
   explicit TransformationCompositeInsert(
-      const protobufs::TransformationCompositeInsert& message);
+      protobufs::TransformationCompositeInsert message);
 
   TransformationCompositeInsert(
       const protobufs::InstructionDescriptor& instruction_to_insert_before,
diff --git a/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp b/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp
index f727052..7ae9df8 100644
--- a/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp
+++ b/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp
@@ -19,9 +19,8 @@
 
 TransformationComputeDataSynonymFactClosure::
     TransformationComputeDataSynonymFactClosure(
-        const spvtools::fuzz::protobufs::
-            TransformationComputeDataSynonymFactClosure& message)
-    : message_(message) {}
+        protobufs::TransformationComputeDataSynonymFactClosure message)
+    : message_(std::move(message)) {}
 
 TransformationComputeDataSynonymFactClosure::
     TransformationComputeDataSynonymFactClosure(
diff --git a/source/fuzz/transformation_compute_data_synonym_fact_closure.h b/source/fuzz/transformation_compute_data_synonym_fact_closure.h
index dedabe2..c61b26e 100644
--- a/source/fuzz/transformation_compute_data_synonym_fact_closure.h
+++ b/source/fuzz/transformation_compute_data_synonym_fact_closure.h
@@ -26,7 +26,7 @@
 class TransformationComputeDataSynonymFactClosure : public Transformation {
  public:
   explicit TransformationComputeDataSynonymFactClosure(
-      const protobufs::TransformationComputeDataSynonymFactClosure& message);
+      protobufs::TransformationComputeDataSynonymFactClosure message);
 
   explicit TransformationComputeDataSynonymFactClosure(
       uint32_t maximum_equivalence_class_size);
diff --git a/source/fuzz/transformation_duplicate_region_with_selection.cpp b/source/fuzz/transformation_duplicate_region_with_selection.cpp
index 2ac6259..a80becd 100644
--- a/source/fuzz/transformation_duplicate_region_with_selection.cpp
+++ b/source/fuzz/transformation_duplicate_region_with_selection.cpp
@@ -21,9 +21,8 @@
 
 TransformationDuplicateRegionWithSelection::
     TransformationDuplicateRegionWithSelection(
-        const spvtools::fuzz::protobufs::
-            TransformationDuplicateRegionWithSelection& message)
-    : message_(message) {}
+        protobufs::TransformationDuplicateRegionWithSelection message)
+    : message_(std::move(message)) {}
 
 TransformationDuplicateRegionWithSelection::
     TransformationDuplicateRegionWithSelection(
@@ -310,9 +309,9 @@
 
   // Construct the merge block.
   std::unique_ptr<opt::BasicBlock> merge_block =
-      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(opt::Instruction(
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
           ir_context, SpvOpLabel, 0, message_.merge_label_fresh_id(),
-          opt::Instruction::OperandList())));
+          opt::Instruction::OperandList()));
 
   // Get the maps from the protobuf.
   std::map<uint32_t, uint32_t> original_label_to_duplicate_label =
diff --git a/source/fuzz/transformation_duplicate_region_with_selection.h b/source/fuzz/transformation_duplicate_region_with_selection.h
index a2b9a43..30e3c37 100644
--- a/source/fuzz/transformation_duplicate_region_with_selection.h
+++ b/source/fuzz/transformation_duplicate_region_with_selection.h
@@ -26,7 +26,7 @@
 class TransformationDuplicateRegionWithSelection : public Transformation {
  public:
   explicit TransformationDuplicateRegionWithSelection(
-      const protobufs::TransformationDuplicateRegionWithSelection& message);
+      protobufs::TransformationDuplicateRegionWithSelection message);
 
   explicit TransformationDuplicateRegionWithSelection(
       uint32_t new_entry_fresh_id, uint32_t condition_id,
diff --git a/source/fuzz/transformation_equation_instruction.cpp b/source/fuzz/transformation_equation_instruction.cpp
index 32e8351..1e5dae9 100644
--- a/source/fuzz/transformation_equation_instruction.cpp
+++ b/source/fuzz/transformation_equation_instruction.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationEquationInstruction::TransformationEquationInstruction(
-    const spvtools::fuzz::protobufs::TransformationEquationInstruction& message)
-    : message_(message) {}
+    protobufs::TransformationEquationInstruction message)
+    : message_(std::move(message)) {}
 
 TransformationEquationInstruction::TransformationEquationInstruction(
     uint32_t fresh_id, SpvOp opcode, const std::vector<uint32_t>& in_operand_id,
@@ -84,13 +84,17 @@
     rhs_id.push_back(id);
   }
 
-  FindInstruction(message_.instruction_to_insert_before(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  opt::Instruction* new_instruction =
+      insert_before->InsertBefore(MakeUnique<opt::Instruction>(
           ir_context, static_cast<SpvOp>(message_.opcode()),
           MaybeGetResultTypeId(ir_context), message_.fresh_id(),
           std::move(in_operands)));
 
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction);
+  ir_context->set_instr_block(new_instruction,
+                              ir_context->get_instr_block(insert_before));
 
   // Add an equation fact as long as the result id is not irrelevant (it could
   // be if we are inserting into a dead block).
diff --git a/source/fuzz/transformation_equation_instruction.h b/source/fuzz/transformation_equation_instruction.h
index 4afc85f..ae32a1a 100644
--- a/source/fuzz/transformation_equation_instruction.h
+++ b/source/fuzz/transformation_equation_instruction.h
@@ -28,7 +28,7 @@
 class TransformationEquationInstruction : public Transformation {
  public:
   explicit TransformationEquationInstruction(
-      const protobufs::TransformationEquationInstruction& message);
+      protobufs::TransformationEquationInstruction message);
 
   TransformationEquationInstruction(
       uint32_t fresh_id, SpvOp opcode,
diff --git a/source/fuzz/transformation_expand_vector_reduction.cpp b/source/fuzz/transformation_expand_vector_reduction.cpp
index 640aea2..bafcf92 100644
--- a/source/fuzz/transformation_expand_vector_reduction.cpp
+++ b/source/fuzz/transformation_expand_vector_reduction.cpp
@@ -21,9 +21,8 @@
 namespace fuzz {
 
 TransformationExpandVectorReduction::TransformationExpandVectorReduction(
-    const spvtools::fuzz::protobufs::TransformationExpandVectorReduction&
-        message)
-    : message_(message) {}
+    protobufs::TransformationExpandVectorReduction message)
+    : message_(std::move(message)) {}
 
 TransformationExpandVectorReduction::TransformationExpandVectorReduction(
     const uint32_t instruction_result_id,
@@ -130,7 +129,7 @@
   // If it's possible to make a synonym of |instruction|, then add the fact that
   // the last |logical_instruction| is a synonym of |instruction|.
   if (fuzzerutil::CanMakeSynonymOf(ir_context, *transformation_context,
-                                   instruction)) {
+                                   *instruction)) {
     transformation_context->GetFactManager()->AddFactDataSynonym(
         MakeDataDescriptor(logical_instruction.result_id(), {}),
         MakeDataDescriptor(instruction->result_id(), {}));
diff --git a/source/fuzz/transformation_expand_vector_reduction.h b/source/fuzz/transformation_expand_vector_reduction.h
index e4cc953..6ee2cef 100644
--- a/source/fuzz/transformation_expand_vector_reduction.h
+++ b/source/fuzz/transformation_expand_vector_reduction.h
@@ -70,7 +70,7 @@
 class TransformationExpandVectorReduction : public Transformation {
  public:
   explicit TransformationExpandVectorReduction(
-      const protobufs::TransformationExpandVectorReduction& message);
+      protobufs::TransformationExpandVectorReduction message);
 
   TransformationExpandVectorReduction(const uint32_t instruction_result_id,
                                       const std::vector<uint32_t>& fresh_ids);
diff --git a/source/fuzz/transformation_flatten_conditional_branch.cpp b/source/fuzz/transformation_flatten_conditional_branch.cpp
index fdee513..127e762 100644
--- a/source/fuzz/transformation_flatten_conditional_branch.cpp
+++ b/source/fuzz/transformation_flatten_conditional_branch.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
-    const protobufs::TransformationFlattenConditionalBranch& message)
-    : message_(message) {}
+    protobufs::TransformationFlattenConditionalBranch message)
+    : message_(std::move(message)) {}
 
 TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
     uint32_t header_block_id, bool true_branch_first,
@@ -441,17 +441,17 @@
          header->terminator()->opcode() == SpvOpBranchConditional &&
          "|header| must be the header of a conditional.");
 
+  // |header| must be reachable.
+  if (!ir_context->IsReachable(*header)) {
+    return false;
+  }
+
   auto enclosing_function = header->GetParent();
   auto dominator_analysis =
       ir_context->GetDominatorAnalysis(enclosing_function);
   auto postdominator_analysis =
       ir_context->GetPostDominatorAnalysis(enclosing_function);
 
-  // |header| must be reachable.
-  if (!dominator_analysis->IsReachable(header)) {
-    return false;
-  }
-
   // Check that the header and the merge block describe a single-entry,
   // single-exit region.
   if (!dominator_analysis->Dominates(header->id(), merge_block_id) ||
@@ -844,7 +844,9 @@
     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_3:
+    case SPV_ENV_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1: {
       return true;
     }
     default:
diff --git a/source/fuzz/transformation_flatten_conditional_branch.h b/source/fuzz/transformation_flatten_conditional_branch.h
index 9bdae93..4efff67 100644
--- a/source/fuzz/transformation_flatten_conditional_branch.h
+++ b/source/fuzz/transformation_flatten_conditional_branch.h
@@ -23,7 +23,7 @@
 class TransformationFlattenConditionalBranch : public Transformation {
  public:
   explicit TransformationFlattenConditionalBranch(
-      const protobufs::TransformationFlattenConditionalBranch& message);
+      protobufs::TransformationFlattenConditionalBranch message);
 
   TransformationFlattenConditionalBranch(
       uint32_t header_block_id, bool true_branch_first,
diff --git a/source/fuzz/transformation_function_call.cpp b/source/fuzz/transformation_function_call.cpp
index ec95c32..0f88ce5 100644
--- a/source/fuzz/transformation_function_call.cpp
+++ b/source/fuzz/transformation_function_call.cpp
@@ -22,8 +22,8 @@
 namespace fuzz {
 
 TransformationFunctionCall::TransformationFunctionCall(
-    const spvtools::fuzz::protobufs::TransformationFunctionCall& message)
-    : message_(message) {}
+    protobufs::TransformationFunctionCall message)
+    : message_(std::move(message)) {}
 
 TransformationFunctionCall::TransformationFunctionCall(
     uint32_t fresh_id, uint32_t callee_id,
diff --git a/source/fuzz/transformation_function_call.h b/source/fuzz/transformation_function_call.h
index e220d83..a2aaf36 100644
--- a/source/fuzz/transformation_function_call.h
+++ b/source/fuzz/transformation_function_call.h
@@ -26,7 +26,7 @@
 class TransformationFunctionCall : public Transformation {
  public:
   explicit TransformationFunctionCall(
-      const protobufs::TransformationFunctionCall& message);
+      protobufs::TransformationFunctionCall message);
 
   TransformationFunctionCall(
       uint32_t fresh_id, uint32_t callee_id,
diff --git a/source/fuzz/transformation_inline_function.cpp b/source/fuzz/transformation_inline_function.cpp
index f997491..a48b817 100644
--- a/source/fuzz/transformation_inline_function.cpp
+++ b/source/fuzz/transformation_inline_function.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationInlineFunction::TransformationInlineFunction(
-    const spvtools::fuzz::protobufs::TransformationInlineFunction& message)
-    : message_(message) {}
+    protobufs::TransformationInlineFunction message)
+    : message_(std::move(message)) {}
 
 TransformationInlineFunction::TransformationInlineFunction(
     uint32_t function_call_id,
diff --git a/source/fuzz/transformation_inline_function.h b/source/fuzz/transformation_inline_function.h
index 8105d92..f4dc410 100644
--- a/source/fuzz/transformation_inline_function.h
+++ b/source/fuzz/transformation_inline_function.h
@@ -26,7 +26,7 @@
 class TransformationInlineFunction : public Transformation {
  public:
   explicit TransformationInlineFunction(
-      const protobufs::TransformationInlineFunction& message);
+      protobufs::TransformationInlineFunction message);
 
   TransformationInlineFunction(
       uint32_t function_call_id,
diff --git a/source/fuzz/transformation_load.cpp b/source/fuzz/transformation_load.cpp
index f8b3513..bf48d99 100644
--- a/source/fuzz/transformation_load.cpp
+++ b/source/fuzz/transformation_load.cpp
@@ -20,15 +20,19 @@
 namespace spvtools {
 namespace fuzz {
 
-TransformationLoad::TransformationLoad(
-    const spvtools::fuzz::protobufs::TransformationLoad& message)
-    : message_(message) {}
+TransformationLoad::TransformationLoad(protobufs::TransformationLoad message)
+    : message_(std::move(message)) {}
 
 TransformationLoad::TransformationLoad(
-    uint32_t fresh_id, uint32_t pointer_id,
+    uint32_t fresh_id, uint32_t pointer_id, bool is_atomic,
+    uint32_t memory_scope, uint32_t memory_semantics,
     const protobufs::InstructionDescriptor& instruction_to_insert_before) {
   message_.set_fresh_id(fresh_id);
   message_.set_pointer_id(pointer_id);
+  message_.set_is_atomic(is_atomic);
+  message_.set_memory_scope_id(memory_scope);
+  message_.set_memory_semantics_id(memory_semantics);
+
   *message_.mutable_instruction_to_insert_before() =
       instruction_to_insert_before;
 }
@@ -69,11 +73,98 @@
   if (!insert_before) {
     return false;
   }
-  // ... and it must be legitimate to insert a store before it.
-  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, insert_before)) {
+  // ... and it must be legitimate to insert a load before it.
+  if (!message_.is_atomic() &&
+      !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, insert_before)) {
     return false;
   }
 
+  if (message_.is_atomic() && !fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                                  SpvOpAtomicLoad, insert_before)) {
+    return false;
+  }
+
+  if (message_.is_atomic()) {
+    // Check the exists of memory scope and memory semantics ids.
+    auto memory_scope_instruction =
+        ir_context->get_def_use_mgr()->GetDef(message_.memory_scope_id());
+    auto memory_semantics_instruction =
+        ir_context->get_def_use_mgr()->GetDef(message_.memory_semantics_id());
+
+    if (!memory_scope_instruction) {
+      return false;
+    }
+    if (!memory_semantics_instruction) {
+      return false;
+    }
+    // The memory scope and memory semantics instructions must have the
+    // 'OpConstant' opcode.
+    if (memory_scope_instruction->opcode() != SpvOpConstant) {
+      return false;
+    }
+    if (memory_semantics_instruction->opcode() != SpvOpConstant) {
+      return false;
+    }
+    // The memory scope and memory semantics need to be available before
+    // |insert_before|.
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+            ir_context, insert_before, message_.memory_scope_id())) {
+      return false;
+    }
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+            ir_context, insert_before, message_.memory_semantics_id())) {
+      return false;
+    }
+    // The memory scope and memory semantics instructions must have an Integer
+    // operand type with signedness does not matters.
+    if (ir_context->get_def_use_mgr()
+            ->GetDef(memory_scope_instruction->type_id())
+            ->opcode() != SpvOpTypeInt) {
+      return false;
+    }
+    if (ir_context->get_def_use_mgr()
+            ->GetDef(memory_semantics_instruction->type_id())
+            ->opcode() != SpvOpTypeInt) {
+      return false;
+    }
+
+    // The size of the integer for memory scope and memory semantics
+    // instructions must be equal to 32 bits.
+    auto memory_scope_int_width =
+        ir_context->get_def_use_mgr()
+            ->GetDef(memory_scope_instruction->type_id())
+            ->GetSingleWordInOperand(0);
+    auto memory_semantics_int_width =
+        ir_context->get_def_use_mgr()
+            ->GetDef(memory_semantics_instruction->type_id())
+            ->GetSingleWordInOperand(0);
+
+    if (memory_scope_int_width != 32) {
+      return false;
+    }
+    if (memory_semantics_int_width != 32) {
+      return false;
+    }
+
+    // The memory scope constant value must be that of SpvScopeInvocation.
+    auto memory_scope_const_value =
+        memory_scope_instruction->GetSingleWordInOperand(0);
+    if (memory_scope_const_value != SpvScopeInvocation) {
+      return false;
+    }
+
+    // The memory semantics constant value must match the storage class of the
+    // pointer being loaded from.
+    auto memory_semantics_const_value = static_cast<SpvMemorySemanticsMask>(
+        memory_semantics_instruction->GetSingleWordInOperand(0));
+    if (memory_semantics_const_value !=
+        fuzzerutil::GetMemorySemanticsForStorageClass(
+            static_cast<SpvStorageClass>(
+                pointer_type->GetSingleWordInOperand(0)))) {
+      return false;
+    }
+  }
+
   // The pointer needs to be available at the insertion point.
   return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
                                                     message_.pointer_id());
@@ -81,15 +172,46 @@
 
 void TransformationLoad::Apply(opt::IRContext* ir_context,
                                TransformationContext* /*unused*/) const {
-  uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
-      ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id()));
-  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  FindInstruction(message_.instruction_to_insert_before(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
-          ir_context, SpvOpLoad, result_type, message_.fresh_id(),
-          opt::Instruction::OperandList(
-              {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}})));
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  if (message_.is_atomic()) {
+    // OpAtomicLoad instruction.
+    uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
+        ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+    auto insert_before =
+        FindInstruction(message_.instruction_to_insert_before(), ir_context);
+    auto new_instruction = MakeUnique<opt::Instruction>(
+        ir_context, SpvOpAtomicLoad, result_type, message_.fresh_id(),
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}},
+             {SPV_OPERAND_TYPE_SCOPE_ID, {message_.memory_scope_id()}},
+             {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID,
+              {message_.memory_semantics_id()}}}));
+    auto new_instruction_ptr = new_instruction.get();
+    insert_before->InsertBefore(std::move(new_instruction));
+    // Inform the def-use manager about the new instruction and record its basic
+    // block.
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+    ir_context->set_instr_block(new_instruction_ptr,
+                                ir_context->get_instr_block(insert_before));
+  } else {
+    // OpLoad instruction.
+    uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
+        ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+    auto insert_before =
+        FindInstruction(message_.instruction_to_insert_before(), ir_context);
+    auto new_instruction = MakeUnique<opt::Instruction>(
+        ir_context, SpvOpLoad, result_type, message_.fresh_id(),
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}}));
+    auto new_instruction_ptr = new_instruction.get();
+    insert_before->InsertBefore(std::move(new_instruction));
+    // Inform the def-use manager about the new instruction and record its basic
+    // block.
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+    ir_context->set_instr_block(new_instruction_ptr,
+                                ir_context->get_instr_block(insert_before));
+  }
 }
 
 protobufs::Transformation TransformationLoad::ToMessage() const {
diff --git a/source/fuzz/transformation_load.h b/source/fuzz/transformation_load.h
index 683bba5..57b4a53 100644
--- a/source/fuzz/transformation_load.h
+++ b/source/fuzz/transformation_load.h
@@ -25,14 +25,23 @@
 
 class TransformationLoad : public Transformation {
  public:
-  explicit TransformationLoad(const protobufs::TransformationLoad& message);
+  explicit TransformationLoad(protobufs::TransformationLoad message);
 
   TransformationLoad(
-      uint32_t fresh_id, uint32_t pointer_id,
+      uint32_t fresh_id, uint32_t pointer_id, bool is_atomic,
+      uint32_t memory_scope, uint32_t memory_semantics,
       const protobufs::InstructionDescriptor& instruction_to_insert_before);
 
   // - |message_.fresh_id| must be fresh
   // - |message_.pointer_id| must be the id of a pointer
+  // - |message_.is_atomic| must be true if want to work with OpAtomicLoad
+  // - If |is_atomic| is true then |message_memory_scope_id| must be the id of
+  //   an OpConstant 32 bit integer instruction with the value
+  //   SpvScopeInvocation.
+  // - If |is_atomic| is true then |message_.memory_semantics_id| must be the id
+  //   of an OpConstant 32 bit integer instruction with the values
+  //   SpvMemorySemanticsWorkgroupMemoryMask or
+  //   SpvMemorySemanticsUniformMemoryMask.
   // - The pointer must not be OpConstantNull or OpUndef
   // - |message_.instruction_to_insert_before| must identify an instruction
   //   before which it is valid to insert an OpLoad, and where
diff --git a/source/fuzz/transformation_make_vector_operation_dynamic.cpp b/source/fuzz/transformation_make_vector_operation_dynamic.cpp
index d6d5140..bd0664c 100644
--- a/source/fuzz/transformation_make_vector_operation_dynamic.cpp
+++ b/source/fuzz/transformation_make_vector_operation_dynamic.cpp
@@ -22,9 +22,8 @@
 
 TransformationMakeVectorOperationDynamic::
     TransformationMakeVectorOperationDynamic(
-        const spvtools::fuzz::protobufs::
-            TransformationMakeVectorOperationDynamic& message)
-    : message_(message) {}
+        protobufs::TransformationMakeVectorOperationDynamic message)
+    : message_(std::move(message)) {}
 
 TransformationMakeVectorOperationDynamic::
     TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id,
diff --git a/source/fuzz/transformation_make_vector_operation_dynamic.h b/source/fuzz/transformation_make_vector_operation_dynamic.h
index d1765c5..e444f40 100644
--- a/source/fuzz/transformation_make_vector_operation_dynamic.h
+++ b/source/fuzz/transformation_make_vector_operation_dynamic.h
@@ -26,7 +26,7 @@
 class TransformationMakeVectorOperationDynamic : public Transformation {
  public:
   explicit TransformationMakeVectorOperationDynamic(
-      const protobufs::TransformationMakeVectorOperationDynamic& message);
+      protobufs::TransformationMakeVectorOperationDynamic message);
 
   TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id,
                                            uint32_t constant_index_id);
diff --git a/source/fuzz/transformation_merge_blocks.cpp b/source/fuzz/transformation_merge_blocks.cpp
index 2a9e90c..dbf782e 100644
--- a/source/fuzz/transformation_merge_blocks.cpp
+++ b/source/fuzz/transformation_merge_blocks.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationMergeBlocks::TransformationMergeBlocks(
-    const spvtools::fuzz::protobufs::TransformationMergeBlocks& message)
-    : message_(message) {}
+    protobufs::TransformationMergeBlocks message)
+    : message_(std::move(message)) {}
 
 TransformationMergeBlocks::TransformationMergeBlocks(uint32_t block_id) {
   message_.set_block_id(block_id);
@@ -43,6 +43,9 @@
   }
   auto first_block = ir_context->cfg()->block(predecessors.at(0));
 
+  if (!ir_context->IsReachable(*first_block)) {
+    return false;
+  }
   return opt::blockmergeutil::CanMergeWithSuccessor(ir_context, first_block);
 }
 
diff --git a/source/fuzz/transformation_merge_blocks.h b/source/fuzz/transformation_merge_blocks.h
index d9a0ca0..f3462fa 100644
--- a/source/fuzz/transformation_merge_blocks.h
+++ b/source/fuzz/transformation_merge_blocks.h
@@ -26,11 +26,12 @@
 class TransformationMergeBlocks : public Transformation {
  public:
   explicit TransformationMergeBlocks(
-      const protobufs::TransformationMergeBlocks& message);
+      protobufs::TransformationMergeBlocks message);
 
   TransformationMergeBlocks(uint32_t block_id);
 
   // - |message_.block_id| must be the id of a block, b
+  // - b must be statically reachable in the control flow graph of its function
   // - b must have a single predecessor, a
   // - b must be the sole successor of a
   // - Replacing a with the merge of a and b (and removing b) must lead to a
diff --git a/source/fuzz/transformation_merge_function_returns.cpp b/source/fuzz/transformation_merge_function_returns.cpp
index c7cb557..022e1b6 100644
--- a/source/fuzz/transformation_merge_function_returns.cpp
+++ b/source/fuzz/transformation_merge_function_returns.cpp
@@ -21,15 +21,17 @@
 namespace fuzz {
 
 TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
-    const protobufs::TransformationMergeFunctionReturns& message)
-    : message_(message) {}
+    protobufs::TransformationMergeFunctionReturns message)
+    : message_(std::move(message)) {}
 
 TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
-    uint32_t function_id, uint32_t outer_header_id, uint32_t outer_return_id,
+    uint32_t function_id, uint32_t outer_header_id,
+    uint32_t unreachable_continue_id, uint32_t outer_return_id,
     uint32_t return_val_id, uint32_t any_returnable_val_id,
     const std::vector<protobufs::ReturnMergingInfo>& returns_merging_info) {
   message_.set_function_id(function_id);
   message_.set_outer_header_id(outer_header_id);
+  message_.set_unreachable_continue_id(unreachable_continue_id);
   message_.set_outer_return_id(outer_return_id);
   message_.set_return_val_id(return_val_id);
   message_.set_any_returnable_val_id(any_returnable_val_id);
@@ -66,7 +68,9 @@
 
   // Check that the fresh ids provided are fresh and distinct.
   std::set<uint32_t> used_fresh_ids;
-  for (uint32_t id : {message_.outer_header_id(), message_.outer_return_id()}) {
+  for (uint32_t id :
+       {message_.outer_header_id(), message_.unreachable_continue_id(),
+        message_.outer_return_id()}) {
     if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
                                                              &used_fresh_ids)) {
       return false;
@@ -499,25 +503,20 @@
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.outer_header_id());
 
-  // Add the instruction: OpLoopMerge %outer_return_id %outer_header_id None
-  // The header is the continue block of the outer loop.
+  // Add the instruction:
+  //   OpLoopMerge %outer_return_id %unreachable_continue_id None
   outer_loop_header->AddInstruction(MakeUnique<opt::Instruction>(
       ir_context, SpvOpLoopMerge, 0, 0,
       opt::Instruction::OperandList{
           {SPV_OPERAND_TYPE_ID, {message_.outer_return_id()}},
-          {SPV_OPERAND_TYPE_ID, {message_.outer_header_id()}},
+          {SPV_OPERAND_TYPE_ID, {message_.unreachable_continue_id()}},
           {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}}));
 
-  // Add conditional branch:
-  //   OpBranchConditional %true %block_after_entry %outer_header_id
-  // This will always branch to %block_after_entry, but it also creates a back
-  // edge for the loop (which is never traversed).
+  // Add unconditional branch to %block_after_entry.
   outer_loop_header->AddInstruction(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpBranchConditional, 0, 0,
+      ir_context, SpvOpBranch, 0, 0,
       opt::Instruction::OperandList{
-          {SPV_OPERAND_TYPE_ID, {constant_true}},
-          {SPV_OPERAND_TYPE_ID, {block_after_entry}},
-          {SPV_OPERAND_TYPE_ID, {message_.outer_header_id()}}}));
+          {SPV_OPERAND_TYPE_ID, {block_after_entry}}}));
 
   // Insert the header right after the entry block.
   function->InsertBasicBlockAfter(std::move(outer_loop_header),
@@ -581,6 +580,24 @@
   // Insert the new return block at the end of the function.
   function->AddBasicBlock(std::move(outer_return_block));
 
+  // Create the unreachable continue block associated with the enclosing loop.
+  auto unreachable_continue_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.unreachable_continue_id(),
+          opt::Instruction::OperandList()));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context,
+                                  message_.unreachable_continue_id());
+
+  // Insert an branch back to the loop header, to create a back edge.
+  unreachable_continue_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranch, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.outer_header_id()}}}));
+
+  // Insert the unreachable continue block at the end of the function.
+  function->AddBasicBlock(std::move(unreachable_continue_block));
+
   // All analyses must be invalidated because the structure of the module was
   // changed.
   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
@@ -590,13 +607,14 @@
     const {
   std::unordered_set<uint32_t> result;
   result.emplace(message_.outer_header_id());
+  result.emplace(message_.unreachable_continue_id());
   result.emplace(message_.outer_return_id());
   // |message_.return_val_info| can be 0 if the function is void.
   if (message_.return_val_id()) {
     result.emplace(message_.return_val_id());
   }
 
-  for (auto merging_info : message_.return_merging_info()) {
+  for (const auto& merging_info : message_.return_merging_info()) {
     result.emplace(merging_info.is_returning_id());
     // |maybe_return_val_id| can be 0 if the function is void.
     if (merging_info.maybe_return_val_id()) {
diff --git a/source/fuzz/transformation_merge_function_returns.h b/source/fuzz/transformation_merge_function_returns.h
index 4b29936..b3208ac 100644
--- a/source/fuzz/transformation_merge_function_returns.h
+++ b/source/fuzz/transformation_merge_function_returns.h
@@ -22,10 +22,11 @@
 class TransformationMergeFunctionReturns : public Transformation {
  public:
   explicit TransformationMergeFunctionReturns(
-      const protobufs::TransformationMergeFunctionReturns& message);
+      protobufs::TransformationMergeFunctionReturns message);
 
   TransformationMergeFunctionReturns(
-      uint32_t function_id, uint32_t outer_header_id, uint32_t outer_return_id,
+      uint32_t function_id, uint32_t outer_header_id,
+      uint32_t unreachable_continue_id, uint32_t outer_return_id,
       uint32_t return_val_id, uint32_t any_returnable_val_id,
       const std::vector<protobufs::ReturnMergingInfo>& returns_merging_info);
 
@@ -40,7 +41,7 @@
   //   statements, this id will be ignored.
   // - Merge blocks of reachable loops that contain return statements only
   //   consist of OpLabel, OpPhi or OpBranch instructions.
-  // - The model contains OpConstantTrue and OpConstantFalse instructions.
+  // - The module contains OpConstantTrue and OpConstantFalse instructions.
   // - For all merge blocks of reachable loops that contain return statements,
   //   either:
   //   - a mapping is provided in |message_.return_merging_info|, all of the
diff --git a/source/fuzz/transformation_move_block_down.cpp b/source/fuzz/transformation_move_block_down.cpp
index c5ed433..dc1b243 100644
--- a/source/fuzz/transformation_move_block_down.cpp
+++ b/source/fuzz/transformation_move_block_down.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationMoveBlockDown::TransformationMoveBlockDown(
-    const spvtools::fuzz::protobufs::TransformationMoveBlockDown& message)
-    : message_(message) {}
+    protobufs::TransformationMoveBlockDown message)
+    : message_(std::move(message)) {}
 
 TransformationMoveBlockDown::TransformationMoveBlockDown(uint32_t id) {
   message_.set_block_id(id);
diff --git a/source/fuzz/transformation_move_block_down.h b/source/fuzz/transformation_move_block_down.h
index 82f2599..cbad945 100644
--- a/source/fuzz/transformation_move_block_down.h
+++ b/source/fuzz/transformation_move_block_down.h
@@ -26,7 +26,7 @@
 class TransformationMoveBlockDown : public Transformation {
  public:
   explicit TransformationMoveBlockDown(
-      const protobufs::TransformationMoveBlockDown& message);
+      protobufs::TransformationMoveBlockDown message);
 
   explicit TransformationMoveBlockDown(uint32_t id);
 
diff --git a/source/fuzz/transformation_move_instruction_down.cpp b/source/fuzz/transformation_move_instruction_down.cpp
index dec0578..c8139e7 100644
--- a/source/fuzz/transformation_move_instruction_down.cpp
+++ b/source/fuzz/transformation_move_instruction_down.cpp
@@ -38,8 +38,8 @@
 }  // namespace
 
 TransformationMoveInstructionDown::TransformationMoveInstructionDown(
-    const protobufs::TransformationMoveInstructionDown& message)
-    : message_(message) {}
+    protobufs::TransformationMoveInstructionDown message)
+    : message_(std::move(message)) {}
 
 TransformationMoveInstructionDown::TransformationMoveInstructionDown(
     const protobufs::InstructionDescriptor& instruction) {
diff --git a/source/fuzz/transformation_move_instruction_down.h b/source/fuzz/transformation_move_instruction_down.h
index 8585225..2a5a8f1 100644
--- a/source/fuzz/transformation_move_instruction_down.h
+++ b/source/fuzz/transformation_move_instruction_down.h
@@ -26,7 +26,7 @@
 class TransformationMoveInstructionDown : public Transformation {
  public:
   explicit TransformationMoveInstructionDown(
-      const protobufs::TransformationMoveInstructionDown& message);
+      protobufs::TransformationMoveInstructionDown message);
 
   explicit TransformationMoveInstructionDown(
       const protobufs::InstructionDescriptor& instruction);
diff --git a/source/fuzz/transformation_mutate_pointer.cpp b/source/fuzz/transformation_mutate_pointer.cpp
index fefedbd..516a0d6 100644
--- a/source/fuzz/transformation_mutate_pointer.cpp
+++ b/source/fuzz/transformation_mutate_pointer.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationMutatePointer::TransformationMutatePointer(
-    const protobufs::TransformationMutatePointer& message)
-    : message_(message) {}
+    protobufs::TransformationMutatePointer message)
+    : message_(std::move(message)) {}
 
 TransformationMutatePointer::TransformationMutatePointer(
     uint32_t pointer_id, uint32_t fresh_id,
@@ -92,36 +92,47 @@
   auto* insert_before_inst =
       FindInstruction(message_.insert_before(), ir_context);
   assert(insert_before_inst && "|insert_before| descriptor is invalid");
+  opt::BasicBlock* enclosing_block =
+      ir_context->get_instr_block(insert_before_inst);
 
   auto pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
       ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id()));
 
   // Back up the original value.
-  insert_before_inst->InsertBefore(MakeUnique<opt::Instruction>(
+  auto backup_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpLoad, pointee_type_id, message_.fresh_id(),
       opt::Instruction::OperandList{
-          {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}}));
+          {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}});
+  auto backup_instruction_ptr = backup_instruction.get();
+  insert_before_inst->InsertBefore(std::move(backup_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(backup_instruction_ptr);
+  ir_context->set_instr_block(backup_instruction_ptr, enclosing_block);
 
   // Insert a new value.
-  insert_before_inst->InsertBefore(MakeUnique<opt::Instruction>(
+  auto new_value_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpStore, 0, 0,
       opt::Instruction::OperandList{
           {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}},
           {SPV_OPERAND_TYPE_ID,
            {fuzzerutil::MaybeGetZeroConstant(
-               ir_context, *transformation_context, pointee_type_id, true)}}}));
+               ir_context, *transformation_context, pointee_type_id, true)}}});
+  auto new_value_instruction_ptr = new_value_instruction.get();
+  insert_before_inst->InsertBefore(std::move(new_value_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_value_instruction_ptr);
+  ir_context->set_instr_block(new_value_instruction_ptr, enclosing_block);
 
   // Restore the original value.
-  insert_before_inst->InsertBefore(MakeUnique<opt::Instruction>(
+  auto restore_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpStore, 0, 0,
       opt::Instruction::OperandList{
           {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}},
-          {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}}));
+          {SPV_OPERAND_TYPE_ID, {message_.fresh_id()}}});
+  auto restore_instruction_ptr = restore_instruction.get();
+  insert_before_inst->InsertBefore(std::move(restore_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(restore_instruction_ptr);
+  ir_context->set_instr_block(restore_instruction_ptr, enclosing_block);
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-
-  // Make sure analyses represent the correct state of the module.
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationMutatePointer::ToMessage() const {
diff --git a/source/fuzz/transformation_mutate_pointer.h b/source/fuzz/transformation_mutate_pointer.h
index b9f0965..2c71290 100644
--- a/source/fuzz/transformation_mutate_pointer.h
+++ b/source/fuzz/transformation_mutate_pointer.h
@@ -26,7 +26,7 @@
 class TransformationMutatePointer : public Transformation {
  public:
   explicit TransformationMutatePointer(
-      const protobufs::TransformationMutatePointer& message);
+      protobufs::TransformationMutatePointer message);
 
   explicit TransformationMutatePointer(
       uint32_t pointer_id, uint32_t fresh_id,
diff --git a/source/fuzz/transformation_outline_function.cpp b/source/fuzz/transformation_outline_function.cpp
index 643fd69..3140fa6 100644
--- a/source/fuzz/transformation_outline_function.cpp
+++ b/source/fuzz/transformation_outline_function.cpp
@@ -22,8 +22,8 @@
 namespace fuzz {
 
 TransformationOutlineFunction::TransformationOutlineFunction(
-    const spvtools::fuzz::protobufs::TransformationOutlineFunction& message)
-    : message_(message) {}
+    protobufs::TransformationOutlineFunction message)
+    : message_(std::move(message)) {}
 
 TransformationOutlineFunction::TransformationOutlineFunction(
     uint32_t entry_block, uint32_t exit_block,
@@ -175,6 +175,18 @@
   // This is achieved by going through every block in the function that contains
   // the region.
   for (auto& block : *entry_block->GetParent()) {
+    if (region_set.count(&block) != 0) {
+      // The block is in the region. Check that it does not have any unreachable
+      // predecessors. If it does, then we do not regard the region as single-
+      // entry-single-exit and hence do not outline it.
+      for (auto pred : ir_context->cfg()->preds(block.id())) {
+        if (!ir_context->IsReachable(*ir_context->cfg()->block(pred))) {
+          // The predecessor is unreachable.
+          return false;
+        }
+      }
+    }
+
     if (&block == exit_block) {
       // It is OK (and typically expected) for the exit block of the region to
       // have successors outside the region.
diff --git a/source/fuzz/transformation_outline_function.h b/source/fuzz/transformation_outline_function.h
index 36c0daf..94ce556 100644
--- a/source/fuzz/transformation_outline_function.h
+++ b/source/fuzz/transformation_outline_function.h
@@ -30,7 +30,7 @@
 class TransformationOutlineFunction : public Transformation {
  public:
   explicit TransformationOutlineFunction(
-      const protobufs::TransformationOutlineFunction& message);
+      protobufs::TransformationOutlineFunction message);
 
   TransformationOutlineFunction(
       uint32_t entry_block, uint32_t exit_block,
diff --git a/source/fuzz/transformation_permute_function_parameters.cpp b/source/fuzz/transformation_permute_function_parameters.cpp
index a954cc1..5663d72 100644
--- a/source/fuzz/transformation_permute_function_parameters.cpp
+++ b/source/fuzz/transformation_permute_function_parameters.cpp
@@ -23,9 +23,8 @@
 
 TransformationPermuteFunctionParameters::
     TransformationPermuteFunctionParameters(
-        const spvtools::fuzz::protobufs::
-            TransformationPermuteFunctionParameters& message)
-    : message_(message) {}
+        protobufs::TransformationPermuteFunctionParameters message)
+    : message_(std::move(message)) {}
 
 TransformationPermuteFunctionParameters::
     TransformationPermuteFunctionParameters(
diff --git a/source/fuzz/transformation_permute_function_parameters.h b/source/fuzz/transformation_permute_function_parameters.h
index 38de8b1..abb5675 100644
--- a/source/fuzz/transformation_permute_function_parameters.h
+++ b/source/fuzz/transformation_permute_function_parameters.h
@@ -26,7 +26,7 @@
 class TransformationPermuteFunctionParameters : public Transformation {
  public:
   explicit TransformationPermuteFunctionParameters(
-      const protobufs::TransformationPermuteFunctionParameters& message);
+      protobufs::TransformationPermuteFunctionParameters message);
 
   TransformationPermuteFunctionParameters(
       uint32_t function_id, uint32_t function_type_fresh_id,
diff --git a/source/fuzz/transformation_permute_phi_operands.cpp b/source/fuzz/transformation_permute_phi_operands.cpp
index ebd3c86..7ee7a82 100644
--- a/source/fuzz/transformation_permute_phi_operands.cpp
+++ b/source/fuzz/transformation_permute_phi_operands.cpp
@@ -22,8 +22,8 @@
 namespace fuzz {
 
 TransformationPermutePhiOperands::TransformationPermutePhiOperands(
-    const spvtools::fuzz::protobufs::TransformationPermutePhiOperands& message)
-    : message_(message) {}
+    protobufs::TransformationPermutePhiOperands message)
+    : message_(std::move(message)) {}
 
 TransformationPermutePhiOperands::TransformationPermutePhiOperands(
     uint32_t result_id, const std::vector<uint32_t>& permutation) {
@@ -80,9 +80,8 @@
 
   inst->SetInOperands(std::move(permuted_operands));
 
-  // Make sure our changes are analyzed
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  // Update the def-use manager.
+  ir_context->UpdateDefUse(inst);
 }
 
 protobufs::Transformation TransformationPermutePhiOperands::ToMessage() const {
diff --git a/source/fuzz/transformation_permute_phi_operands.h b/source/fuzz/transformation_permute_phi_operands.h
index 8198b70..1642711 100644
--- a/source/fuzz/transformation_permute_phi_operands.h
+++ b/source/fuzz/transformation_permute_phi_operands.h
@@ -26,7 +26,7 @@
 class TransformationPermutePhiOperands : public Transformation {
  public:
   explicit TransformationPermutePhiOperands(
-      const protobufs::TransformationPermutePhiOperands& message);
+      protobufs::TransformationPermutePhiOperands message);
 
   TransformationPermutePhiOperands(uint32_t result_id,
                                    const std::vector<uint32_t>& permutation);
diff --git a/source/fuzz/transformation_propagate_instruction_down.cpp b/source/fuzz/transformation_propagate_instruction_down.cpp
index ba22e39..c3b7c4d 100644
--- a/source/fuzz/transformation_propagate_instruction_down.cpp
+++ b/source/fuzz/transformation_propagate_instruction_down.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
-    const protobufs::TransformationPropagateInstructionDown& message)
-    : message_(message) {}
+    protobufs::TransformationPropagateInstructionDown message)
+    : message_(std::move(message)) {}
 
 TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
     uint32_t block_id, uint32_t phi_fresh_id,
@@ -386,11 +386,8 @@
     return false;
   }
 
-  const auto* dominator_analysis =
-      ir_context->GetDominatorAnalysis(block->GetParent());
-
   // |block| must be reachable.
-  if (!dominator_analysis->IsReachable(block)) {
+  if (!ir_context->IsReachable(*block)) {
     return false;
   }
 
@@ -430,6 +427,9 @@
   auto phi_block_id =
       GetOpPhiBlockId(ir_context, block_id, *inst_to_propagate, successor_ids);
 
+  const auto* dominator_analysis =
+      ir_context->GetDominatorAnalysis(block->GetParent());
+
   // Make sure we can adjust all users of the propagated instruction.
   return ir_context->get_def_use_mgr()->WhileEachUse(
       inst_to_propagate,
@@ -537,7 +537,7 @@
 
   // Check that |merge_block_id| is reachable in the CFG and |block_id|
   // dominates |merge_block_id|.
-  if (!dominator_analysis->IsReachable(merge_block_id) ||
+  if (!ir_context->IsReachable(*ir_context->cfg()->block(merge_block_id)) ||
       !dominator_analysis->Dominates(block_id, merge_block_id)) {
     return 0;
   }
diff --git a/source/fuzz/transformation_propagate_instruction_down.h b/source/fuzz/transformation_propagate_instruction_down.h
index 7eca1ad..560d7dc 100644
--- a/source/fuzz/transformation_propagate_instruction_down.h
+++ b/source/fuzz/transformation_propagate_instruction_down.h
@@ -28,7 +28,7 @@
 class TransformationPropagateInstructionDown : public Transformation {
  public:
   explicit TransformationPropagateInstructionDown(
-      const protobufs::TransformationPropagateInstructionDown& message);
+      protobufs::TransformationPropagateInstructionDown message);
 
   TransformationPropagateInstructionDown(
       uint32_t block_id, uint32_t phi_fresh_id,
diff --git a/source/fuzz/transformation_propagate_instruction_up.cpp b/source/fuzz/transformation_propagate_instruction_up.cpp
index a2cacf4..bf0e663 100644
--- a/source/fuzz/transformation_propagate_instruction_up.cpp
+++ b/source/fuzz/transformation_propagate_instruction_up.cpp
@@ -79,8 +79,8 @@
 }  // namespace
 
 TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
-    const protobufs::TransformationPropagateInstructionUp& message)
-    : message_(message) {}
+    protobufs::TransformationPropagateInstructionUp message)
+    : message_(std::move(message)) {}
 
 TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
     uint32_t block_id,
diff --git a/source/fuzz/transformation_propagate_instruction_up.h b/source/fuzz/transformation_propagate_instruction_up.h
index 6354094..0ca051b 100644
--- a/source/fuzz/transformation_propagate_instruction_up.h
+++ b/source/fuzz/transformation_propagate_instruction_up.h
@@ -28,7 +28,7 @@
 class TransformationPropagateInstructionUp : public Transformation {
  public:
   explicit TransformationPropagateInstructionUp(
-      const protobufs::TransformationPropagateInstructionUp& message);
+      protobufs::TransformationPropagateInstructionUp message);
 
   TransformationPropagateInstructionUp(
       uint32_t block_id,
diff --git a/source/fuzz/transformation_push_id_through_variable.cpp b/source/fuzz/transformation_push_id_through_variable.cpp
index cdc40aa..55a57a1 100644
--- a/source/fuzz/transformation_push_id_through_variable.cpp
+++ b/source/fuzz/transformation_push_id_through_variable.cpp
@@ -21,9 +21,8 @@
 namespace fuzz {
 
 TransformationPushIdThroughVariable::TransformationPushIdThroughVariable(
-    const spvtools::fuzz::protobufs::TransformationPushIdThroughVariable&
-        message)
-    : message_(message) {}
+    protobufs::TransformationPushIdThroughVariable message)
+    : message_(std::move(message)) {}
 
 TransformationPushIdThroughVariable::TransformationPushIdThroughVariable(
     uint32_t value_id, uint32_t value_synonym_id, uint32_t variable_id,
@@ -62,7 +61,7 @@
 
   // The instruction to insert before must belong to a reachable block.
   auto basic_block = ir_context->get_instr_block(instruction_to_insert_before);
-  if (!fuzzerutil::BlockIsReachableInItsFunction(ir_context, basic_block)) {
+  if (!ir_context->IsReachable(*basic_block)) {
     return false;
   }
 
@@ -105,6 +104,10 @@
   auto value_instruction =
       ir_context->get_def_use_mgr()->GetDef(message_.value_id());
 
+  opt::Instruction* insert_before =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+  opt::BasicBlock* enclosing_block = ir_context->get_instr_block(insert_before);
+
   // A pointer type instruction pointing to the value type must be defined.
   auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
       ir_context, value_instruction->type_id(),
@@ -113,40 +116,46 @@
 
   // Adds whether a global or local variable.
   if (message_.variable_storage_class() == SpvStorageClassPrivate) {
-    fuzzerutil::AddGlobalVariable(ir_context, message_.variable_id(),
-                                  pointer_type_id, SpvStorageClassPrivate,
-                                  message_.initializer_id());
+    opt::Instruction* global_variable = fuzzerutil::AddGlobalVariable(
+        ir_context, message_.variable_id(), pointer_type_id,
+        SpvStorageClassPrivate, message_.initializer_id());
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(global_variable);
   } else {
-    auto function_id = ir_context
-                           ->get_instr_block(FindInstruction(
-                               message_.instruction_descriptor(), ir_context))
-                           ->GetParent()
-                           ->result_id();
-    fuzzerutil::AddLocalVariable(ir_context, message_.variable_id(),
-                                 pointer_type_id, function_id,
-                                 message_.initializer_id());
+    opt::Function* function =
+        ir_context
+            ->get_instr_block(
+                FindInstruction(message_.instruction_descriptor(), ir_context))
+            ->GetParent();
+    opt::Instruction* local_variable = fuzzerutil::AddLocalVariable(
+        ir_context, message_.variable_id(), pointer_type_id,
+        function->result_id(), message_.initializer_id());
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(local_variable);
+    ir_context->set_instr_block(local_variable, &*function->entry());
   }
 
   // First, insert the OpLoad instruction before |instruction_descriptor| and
   // then insert the OpStore instruction before the OpLoad instruction.
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.value_synonym_id());
-  FindInstruction(message_.instruction_descriptor(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
+  opt::Instruction* load_instruction =
+      insert_before->InsertBefore(MakeUnique<opt::Instruction>(
           ir_context, SpvOpLoad, value_instruction->type_id(),
           message_.value_synonym_id(),
           opt::Instruction::OperandList(
-              {{SPV_OPERAND_TYPE_ID, {message_.variable_id()}}})))
-      ->InsertBefore(MakeUnique<opt::Instruction>(
+              {{SPV_OPERAND_TYPE_ID, {message_.variable_id()}}})));
+  opt::Instruction* store_instruction =
+      load_instruction->InsertBefore(MakeUnique<opt::Instruction>(
           ir_context, SpvOpStore, 0, 0,
           opt::Instruction::OperandList(
               {{SPV_OPERAND_TYPE_ID, {message_.variable_id()}},
                {SPV_OPERAND_TYPE_ID, {message_.value_id()}}})));
-
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(store_instruction);
+  ir_context->set_instr_block(store_instruction, enclosing_block);
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(load_instruction);
+  ir_context->set_instr_block(load_instruction, enclosing_block);
 
   // We should be able to create a synonym of |value_id| if it's not irrelevant.
   if (fuzzerutil::CanMakeSynonymOf(ir_context, *transformation_context,
-                                   value_instruction) &&
+                                   *value_instruction) &&
       !transformation_context->GetFactManager()->IdIsIrrelevant(
           message_.value_synonym_id())) {
     // Adds the fact that |message_.value_synonym_id|
diff --git a/source/fuzz/transformation_push_id_through_variable.h b/source/fuzz/transformation_push_id_through_variable.h
index d055825..ec6943c 100644
--- a/source/fuzz/transformation_push_id_through_variable.h
+++ b/source/fuzz/transformation_push_id_through_variable.h
@@ -26,7 +26,7 @@
 class TransformationPushIdThroughVariable : public Transformation {
  public:
   explicit TransformationPushIdThroughVariable(
-      const protobufs::TransformationPushIdThroughVariable& message);
+      protobufs::TransformationPushIdThroughVariable message);
 
   TransformationPushIdThroughVariable(
       uint32_t value_id, uint32_t value_synonym_fresh_id,
diff --git a/source/fuzz/transformation_record_synonymous_constants.cpp b/source/fuzz/transformation_record_synonymous_constants.cpp
index 30ea94b..3278d7d 100644
--- a/source/fuzz/transformation_record_synonymous_constants.cpp
+++ b/source/fuzz/transformation_record_synonymous_constants.cpp
@@ -22,8 +22,8 @@
 
 TransformationRecordSynonymousConstants::
     TransformationRecordSynonymousConstants(
-        const protobufs::TransformationRecordSynonymousConstants& message)
-    : message_(message) {}
+        protobufs::TransformationRecordSynonymousConstants message)
+    : message_(std::move(message)) {}
 
 TransformationRecordSynonymousConstants::
     TransformationRecordSynonymousConstants(uint32_t constant1_id,
diff --git a/source/fuzz/transformation_record_synonymous_constants.h b/source/fuzz/transformation_record_synonymous_constants.h
index 4376c87..d99b0e2 100644
--- a/source/fuzz/transformation_record_synonymous_constants.h
+++ b/source/fuzz/transformation_record_synonymous_constants.h
@@ -24,7 +24,7 @@
 class TransformationRecordSynonymousConstants : public Transformation {
  public:
   explicit TransformationRecordSynonymousConstants(
-      const protobufs::TransformationRecordSynonymousConstants& message);
+      protobufs::TransformationRecordSynonymousConstants message);
 
   TransformationRecordSynonymousConstants(uint32_t constant1_id,
                                           uint32_t constant2_id);
diff --git a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp
index a257515..e1977a6 100644
--- a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp
+++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp
@@ -27,9 +27,8 @@
 
 TransformationReplaceAddSubMulWithCarryingExtended::
     TransformationReplaceAddSubMulWithCarryingExtended(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceAddSubMulWithCarryingExtended& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceAddSubMulWithCarryingExtended message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceAddSubMulWithCarryingExtended::
     TransformationReplaceAddSubMulWithCarryingExtended(uint32_t struct_fresh_id,
diff --git a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h
index 243542c..9deb280 100644
--- a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h
+++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h
@@ -27,8 +27,7 @@
     : public Transformation {
  public:
   explicit TransformationReplaceAddSubMulWithCarryingExtended(
-      const protobufs::TransformationReplaceAddSubMulWithCarryingExtended&
-          message);
+      protobufs::TransformationReplaceAddSubMulWithCarryingExtended message);
 
   explicit TransformationReplaceAddSubMulWithCarryingExtended(
       uint32_t struct_fresh_id, uint32_t result_id);
diff --git a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
index b458b56..2429351 100644
--- a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
@@ -111,9 +111,9 @@
 
 TransformationReplaceBooleanConstantWithConstantBinary::
     TransformationReplaceBooleanConstantWithConstantBinary(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceBooleanConstantWithConstantBinary& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceBooleanConstantWithConstantBinary
+            message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceBooleanConstantWithConstantBinary::
     TransformationReplaceBooleanConstantWithConstantBinary(
diff --git a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
index a0ece7f..97c66bf 100644
--- a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
@@ -27,7 +27,7 @@
     : public Transformation {
  public:
   explicit TransformationReplaceBooleanConstantWithConstantBinary(
-      const protobufs::TransformationReplaceBooleanConstantWithConstantBinary&
+      protobufs::TransformationReplaceBooleanConstantWithConstantBinary
           message);
 
   TransformationReplaceBooleanConstantWithConstantBinary(
diff --git a/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.cpp b/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.cpp
index e809012..9ea7cb6 100644
--- a/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.cpp
+++ b/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.cpp
@@ -21,9 +21,8 @@
 
 TransformationReplaceBranchFromDeadBlockWithExit::
     TransformationReplaceBranchFromDeadBlockWithExit(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceBranchFromDeadBlockWithExit& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceBranchFromDeadBlockWithExit message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceBranchFromDeadBlockWithExit::
     TransformationReplaceBranchFromDeadBlockWithExit(uint32_t block_id,
@@ -162,7 +161,10 @@
   if (ir_context->cfg()->preds(successor->id()).size() < 2) {
     return false;
   }
-  return true;
+  // Make sure that domination rules are satisfied when we remove the branch
+  // from the |block| to its |successor|.
+  return fuzzerutil::NewTerminatorPreservesDominationRules(
+      ir_context, block.id(), {ir_context, SpvOpUnreachable});
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h b/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h
index e1418c9..89667fc 100644
--- a/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h
+++ b/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h
@@ -27,8 +27,7 @@
 class TransformationReplaceBranchFromDeadBlockWithExit : public Transformation {
  public:
   explicit TransformationReplaceBranchFromDeadBlockWithExit(
-      const protobufs::TransformationReplaceBranchFromDeadBlockWithExit&
-          message);
+      protobufs::TransformationReplaceBranchFromDeadBlockWithExit message);
 
   TransformationReplaceBranchFromDeadBlockWithExit(uint32_t block_id,
                                                    SpvOp opcode,
@@ -41,13 +40,17 @@
   //   predecessor
   // - |message_.opcode()| must be one of OpKill, OpReturn, OpReturnValue and
   //   OpUnreachable
-  // - |message_.opcode()| can only be OpKill the module's entry points all
+  // - |message_.opcode()| can only be OpKill if the module's entry points all
   //   have Fragment execution mode
   // - |message_.opcode()| can only be OpReturn if the return type of the
   //   function containing the block is void
   // - If |message_.opcode()| is OpReturnValue then |message_.return_value_id|
   //   must be an id that is available at the block terminator and that matches
   //   the return type of the enclosing function
+  // - Domination rules should be preserved when we apply this transformation.
+  //   In particular, if some block appears after the |block_id|'s successor in
+  //   the CFG, then that block cannot dominate |block_id|'s successor when this
+  //   transformation is applied.
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.cpp b/source/fuzz/transformation_replace_constant_with_uniform.cpp
index 95932bf..c6698c0 100644
--- a/source/fuzz/transformation_replace_constant_with_uniform.cpp
+++ b/source/fuzz/transformation_replace_constant_with_uniform.cpp
@@ -22,9 +22,8 @@
 
 TransformationReplaceConstantWithUniform::
     TransformationReplaceConstantWithUniform(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceConstantWithUniform& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceConstantWithUniform message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceConstantWithUniform::
     TransformationReplaceConstantWithUniform(
@@ -254,28 +253,39 @@
   auto* insert_before_inst = GetInsertBeforeInstruction(ir_context);
   assert(insert_before_inst &&
          "There must exist an insertion point for OpAccessChain and OpLoad");
+  opt::BasicBlock* enclosing_block =
+      ir_context->get_instr_block(insert_before_inst);
 
   // Add an access chain instruction to target the uniform element.
-  insert_before_inst->InsertBefore(
-      MakeAccessChainInstruction(ir_context, constant_type_id));
+  auto access_chain_instruction =
+      MakeAccessChainInstruction(ir_context, constant_type_id);
+  auto access_chain_instruction_ptr = access_chain_instruction.get();
+  insert_before_inst->InsertBefore(std::move(access_chain_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(
+      access_chain_instruction_ptr);
+  ir_context->set_instr_block(access_chain_instruction_ptr, enclosing_block);
 
   // Add a load from this access chain.
-  insert_before_inst->InsertBefore(
-      MakeLoadInstruction(ir_context, constant_type_id));
+  auto load_instruction = MakeLoadInstruction(ir_context, constant_type_id);
+  auto load_instruction_ptr = load_instruction.get();
+  insert_before_inst->InsertBefore(std::move(load_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(load_instruction_ptr);
+  ir_context->set_instr_block(load_instruction_ptr, enclosing_block);
 
   // 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()});
+  ir_context->get_def_use_mgr()->EraseUseRecordsOfOperandIds(
+      instruction_containing_constant_use);
+  ir_context->get_def_use_mgr()->AnalyzeInstUse(
+      instruction_containing_constant_use);
 
   // Update the module id bound to reflect the new instructions.
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id_for_load());
   fuzzerutil::UpdateModuleIdBound(ir_context,
                                   message_.fresh_id_for_access_chain());
-
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationReplaceConstantWithUniform::ToMessage()
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.h b/source/fuzz/transformation_replace_constant_with_uniform.h
index 9e09748..2121092 100644
--- a/source/fuzz/transformation_replace_constant_with_uniform.h
+++ b/source/fuzz/transformation_replace_constant_with_uniform.h
@@ -28,7 +28,7 @@
 class TransformationReplaceConstantWithUniform : public Transformation {
  public:
   explicit TransformationReplaceConstantWithUniform(
-      const protobufs::TransformationReplaceConstantWithUniform& message);
+      protobufs::TransformationReplaceConstantWithUniform message);
 
   TransformationReplaceConstantWithUniform(
       protobufs::IdUseDescriptor id_use,
diff --git a/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp b/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp
index 936b054..de9d1fd 100644
--- a/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp
+++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp
@@ -22,9 +22,8 @@
 
 TransformationReplaceCopyMemoryWithLoadStore::
     TransformationReplaceCopyMemoryWithLoadStore(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceCopyMemoryWithLoadStore& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceCopyMemoryWithLoadStore message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceCopyMemoryWithLoadStore::
     TransformationReplaceCopyMemoryWithLoadStore(
diff --git a/source/fuzz/transformation_replace_copy_memory_with_load_store.h b/source/fuzz/transformation_replace_copy_memory_with_load_store.h
index 67d349f..55d1e06 100644
--- a/source/fuzz/transformation_replace_copy_memory_with_load_store.h
+++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.h
@@ -26,7 +26,7 @@
 class TransformationReplaceCopyMemoryWithLoadStore : public Transformation {
  public:
   explicit TransformationReplaceCopyMemoryWithLoadStore(
-      const protobufs::TransformationReplaceCopyMemoryWithLoadStore& message);
+      protobufs::TransformationReplaceCopyMemoryWithLoadStore message);
 
   TransformationReplaceCopyMemoryWithLoadStore(
       uint32_t fresh_id, const protobufs::InstructionDescriptor&
diff --git a/source/fuzz/transformation_replace_copy_object_with_store_load.cpp b/source/fuzz/transformation_replace_copy_object_with_store_load.cpp
index 54c99d5..e0643bf 100644
--- a/source/fuzz/transformation_replace_copy_object_with_store_load.cpp
+++ b/source/fuzz/transformation_replace_copy_object_with_store_load.cpp
@@ -22,9 +22,8 @@
 
 TransformationReplaceCopyObjectWithStoreLoad::
     TransformationReplaceCopyObjectWithStoreLoad(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceCopyObjectWithStoreLoad& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceCopyObjectWithStoreLoad message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceCopyObjectWithStoreLoad::
     TransformationReplaceCopyObjectWithStoreLoad(
@@ -88,6 +87,10 @@
   assert(copy_object_instruction &&
          copy_object_instruction->opcode() == SpvOpCopyObject &&
          "The required OpCopyObject instruction must be defined.");
+
+  opt::BasicBlock* enclosing_block =
+      ir_context->get_instr_block(copy_object_instruction);
+
   // Get id used as a source by the OpCopyObject instruction.
   uint32_t src_operand = copy_object_instruction->GetSingleWordInOperand(0);
   // A pointer type instruction pointing to the value type must be defined.
@@ -98,37 +101,46 @@
 
   // Adds a global or local variable (according to the storage class).
   if (message_.variable_storage_class() == SpvStorageClassPrivate) {
-    fuzzerutil::AddGlobalVariable(ir_context, message_.fresh_variable_id(),
-                                  pointer_type_id, SpvStorageClassPrivate,
-                                  message_.variable_initializer_id());
+    opt::Instruction* new_global = fuzzerutil::AddGlobalVariable(
+        ir_context, message_.fresh_variable_id(), pointer_type_id,
+        SpvStorageClassPrivate, message_.variable_initializer_id());
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_global);
   } else {
-    auto function_id = ir_context->get_instr_block(copy_object_instruction)
-                           ->GetParent()
-                           ->result_id();
-    fuzzerutil::AddLocalVariable(ir_context, message_.fresh_variable_id(),
-                                 pointer_type_id, function_id,
-                                 message_.variable_initializer_id());
+    opt::Function* function =
+        ir_context->get_instr_block(copy_object_instruction)->GetParent();
+    opt::Instruction* new_local = fuzzerutil::AddLocalVariable(
+        ir_context, message_.fresh_variable_id(), pointer_type_id,
+        function->result_id(), message_.variable_initializer_id());
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_local);
+    ir_context->set_instr_block(new_local, &*function->begin());
   }
 
   // First, insert the OpLoad instruction before the OpCopyObject instruction
   // and then insert the OpStore instruction before the OpLoad instruction.
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_variable_id());
-  copy_object_instruction
-      ->InsertBefore(MakeUnique<opt::Instruction>(
+  opt::Instruction* load_instruction =
+      copy_object_instruction->InsertBefore(MakeUnique<opt::Instruction>(
           ir_context, SpvOpLoad, copy_object_instruction->type_id(),
           message_.copy_object_result_id(),
           opt::Instruction::OperandList(
-              {{SPV_OPERAND_TYPE_ID, {message_.fresh_variable_id()}}})))
-      ->InsertBefore(MakeUnique<opt::Instruction>(
+              {{SPV_OPERAND_TYPE_ID, {message_.fresh_variable_id()}}})));
+  opt::Instruction* store_instruction =
+      load_instruction->InsertBefore(MakeUnique<opt::Instruction>(
           ir_context, SpvOpStore, 0, 0,
           opt::Instruction::OperandList(
               {{SPV_OPERAND_TYPE_ID, {message_.fresh_variable_id()}},
                {SPV_OPERAND_TYPE_ID, {src_operand}}})));
+
+  // Register the new instructions with the def-use manager, and record their
+  // enclosing block.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(store_instruction);
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(load_instruction);
+  ir_context->set_instr_block(store_instruction, enclosing_block);
+  ir_context->set_instr_block(load_instruction, enclosing_block);
+
   // Remove the CopyObject instruction.
   ir_context->KillInst(copy_object_instruction);
 
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
-
   if (!transformation_context->GetFactManager()->IdIsIrrelevant(
           message_.copy_object_result_id()) &&
       !transformation_context->GetFactManager()->IdIsIrrelevant(src_operand)) {
diff --git a/source/fuzz/transformation_replace_copy_object_with_store_load.h b/source/fuzz/transformation_replace_copy_object_with_store_load.h
index a90905c..8c5ce9e 100644
--- a/source/fuzz/transformation_replace_copy_object_with_store_load.h
+++ b/source/fuzz/transformation_replace_copy_object_with_store_load.h
@@ -26,7 +26,7 @@
 class TransformationReplaceCopyObjectWithStoreLoad : public Transformation {
  public:
   explicit TransformationReplaceCopyObjectWithStoreLoad(
-      const protobufs::TransformationReplaceCopyObjectWithStoreLoad& message);
+      protobufs::TransformationReplaceCopyObjectWithStoreLoad message);
 
   TransformationReplaceCopyObjectWithStoreLoad(
       uint32_t copy_object_result_id, uint32_t fresh_variable_id,
diff --git a/source/fuzz/transformation_replace_id_with_synonym.cpp b/source/fuzz/transformation_replace_id_with_synonym.cpp
index 24e079f..8d21d23 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.cpp
+++ b/source/fuzz/transformation_replace_id_with_synonym.cpp
@@ -26,9 +26,8 @@
 namespace fuzz {
 
 TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
-    const spvtools::fuzz::protobufs::TransformationReplaceIdWithSynonym&
-        message)
-    : message_(message) {}
+    protobufs::TransformationReplaceIdWithSynonym message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceIdWithSynonym::TransformationReplaceIdWithSynonym(
     protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id) {
@@ -66,9 +65,10 @@
   // If the id of interest and the synonym are scalar or vector integer
   // constants with different signedness, their use can only be swapped if the
   // instruction is agnostic to the signedness of the operand.
-  if (!TypesAreCompatible(ir_context, use_instruction->opcode(),
-                          message_.id_use_descriptor().in_operand_index(),
-                          type_id_of_interest, type_id_synonym)) {
+  if (!fuzzerutil::TypesAreCompatible(
+          ir_context, use_instruction->opcode(),
+          message_.id_use_descriptor().in_operand_index(), type_id_of_interest,
+          type_id_synonym)) {
     return false;
   }
 
@@ -95,8 +95,12 @@
   instruction_to_change->SetInOperand(
       message_.id_use_descriptor().in_operand_index(),
       {message_.synonymous_id()});
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->get_def_use_mgr()->EraseUseRecordsOfOperandIds(
+      instruction_to_change);
+  ir_context->get_def_use_mgr()->AnalyzeInstUse(instruction_to_change);
+
+  // No analyses need to be invalidated, since the transformation is local to a
+  // block, and the def-use analysis has been updated.
 }
 
 protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage()
@@ -106,59 +110,6 @@
   return result;
 }
 
-// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3582): Add all
-//  opcodes that are agnostic to signedness of operands to function.
-//  This is not exhaustive yet.
-bool TransformationReplaceIdWithSynonym::IsAgnosticToSignednessOfOperand(
-    SpvOp opcode, uint32_t use_in_operand_index) {
-  switch (opcode) {
-    case SpvOpSNegate:
-    case SpvOpNot:
-    case SpvOpIAdd:
-    case SpvOpISub:
-    case SpvOpIMul:
-    case SpvOpSDiv:
-    case SpvOpSRem:
-    case SpvOpSMod:
-    case SpvOpShiftRightLogical:
-    case SpvOpShiftRightArithmetic:
-    case SpvOpShiftLeftLogical:
-    case SpvOpBitwiseOr:
-    case SpvOpBitwiseXor:
-    case SpvOpBitwiseAnd:
-    case SpvOpIEqual:
-    case SpvOpINotEqual:
-    case SpvOpULessThan:
-    case SpvOpSLessThan:
-    case SpvOpUGreaterThan:
-    case SpvOpSGreaterThan:
-    case SpvOpULessThanEqual:
-    case SpvOpSLessThanEqual:
-    case SpvOpUGreaterThanEqual:
-    case SpvOpSGreaterThanEqual:
-      return true;
-    case SpvOpAccessChain:
-      // The signedness of indices does not matter.
-      return use_in_operand_index > 0;
-    default:
-      // Conservatively assume that the id cannot be swapped in other
-      // instructions.
-      return false;
-  }
-}
-
-bool TransformationReplaceIdWithSynonym::TypesAreCompatible(
-    opt::IRContext* ir_context, SpvOp opcode, uint32_t use_in_operand_index,
-    uint32_t type_id_1, uint32_t type_id_2) {
-  assert(ir_context->get_type_mgr()->GetType(type_id_1) &&
-         ir_context->get_type_mgr()->GetType(type_id_2) &&
-         "Type ids are invalid");
-
-  return type_id_1 == type_id_2 ||
-         (IsAgnosticToSignednessOfOperand(opcode, use_in_operand_index) &&
-          fuzzerutil::TypesAreEqualUpToSign(ir_context, type_id_1, type_id_2));
-}
-
 std::unordered_set<uint32_t> TransformationReplaceIdWithSynonym::GetFreshIds()
     const {
   return std::unordered_set<uint32_t>();
diff --git a/source/fuzz/transformation_replace_id_with_synonym.h b/source/fuzz/transformation_replace_id_with_synonym.h
index 3101710..4570fce 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.h
+++ b/source/fuzz/transformation_replace_id_with_synonym.h
@@ -26,7 +26,7 @@
 class TransformationReplaceIdWithSynonym : public Transformation {
  public:
   explicit TransformationReplaceIdWithSynonym(
-      const protobufs::TransformationReplaceIdWithSynonym& message);
+      protobufs::TransformationReplaceIdWithSynonym message);
 
   TransformationReplaceIdWithSynonym(
       protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id);
@@ -52,22 +52,7 @@
 
   protobufs::Transformation ToMessage() const override;
 
-  // Returns true if |type_id_1| and |type_id_2| represent compatible types
-  // given the context of the instruction with |opcode| (i.e. we can replace
-  // an operand of |opcode| of the first type with an id of the second type
-  // and vice-versa).
-  static bool TypesAreCompatible(opt::IRContext* ir_context, SpvOp opcode,
-                                 uint32_t use_in_operand_index,
-                                 uint32_t type_id_1, uint32_t type_id_2);
-
  private:
-  // Returns true if the instruction with opcode |opcode| does not change its
-  // behaviour depending on the signedness of the operand at
-  // |use_in_operand_index|.
-  // Assumes that the operand must be the id of an integer scalar or vector.
-  static bool IsAgnosticToSignednessOfOperand(SpvOp opcode,
-                                              uint32_t use_in_operand_index);
-
   protobufs::TransformationReplaceIdWithSynonym message_;
 };
 
diff --git a/source/fuzz/transformation_replace_irrelevant_id.cpp b/source/fuzz/transformation_replace_irrelevant_id.cpp
index 27f56eb..a71f96a 100644
--- a/source/fuzz/transformation_replace_irrelevant_id.cpp
+++ b/source/fuzz/transformation_replace_irrelevant_id.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId(
-    const protobufs::TransformationReplaceIrrelevantId& message)
-    : message_(message) {}
+    protobufs::TransformationReplaceIrrelevantId message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId(
     const protobufs::IdUseDescriptor& id_use_descriptor,
@@ -107,9 +107,12 @@
       message_.id_use_descriptor().in_operand_index(),
       {message_.replacement_id()});
 
-  // Invalidate the analyses, since the usage of ids has been changed.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->get_def_use_mgr()->EraseUseRecordsOfOperandIds(
+      instruction_to_change);
+  ir_context->get_def_use_mgr()->AnalyzeInstUse(instruction_to_change);
+
+  // No analyses need to be invalidated, since the transformation is local to a
+  // block, and the def-use analysis has been updated.
 }
 
 protobufs::Transformation TransformationReplaceIrrelevantId::ToMessage() const {
diff --git a/source/fuzz/transformation_replace_irrelevant_id.h b/source/fuzz/transformation_replace_irrelevant_id.h
index 35b1987..e6210b4 100644
--- a/source/fuzz/transformation_replace_irrelevant_id.h
+++ b/source/fuzz/transformation_replace_irrelevant_id.h
@@ -23,7 +23,7 @@
 class TransformationReplaceIrrelevantId : public Transformation {
  public:
   explicit TransformationReplaceIrrelevantId(
-      const protobufs::TransformationReplaceIrrelevantId& message);
+      protobufs::TransformationReplaceIrrelevantId message);
 
   TransformationReplaceIrrelevantId(
       const protobufs::IdUseDescriptor& id_use_descriptor,
diff --git a/source/fuzz/transformation_replace_linear_algebra_instruction.cpp b/source/fuzz/transformation_replace_linear_algebra_instruction.cpp
index fc73a26..2430cca 100644
--- a/source/fuzz/transformation_replace_linear_algebra_instruction.cpp
+++ b/source/fuzz/transformation_replace_linear_algebra_instruction.cpp
@@ -22,9 +22,8 @@
 
 TransformationReplaceLinearAlgebraInstruction::
     TransformationReplaceLinearAlgebraInstruction(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceLinearAlgebraInstruction& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceLinearAlgebraInstruction message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceLinearAlgebraInstruction::
     TransformationReplaceLinearAlgebraInstruction(
diff --git a/source/fuzz/transformation_replace_linear_algebra_instruction.h b/source/fuzz/transformation_replace_linear_algebra_instruction.h
index 45f4aa6..0f0c18b 100644
--- a/source/fuzz/transformation_replace_linear_algebra_instruction.h
+++ b/source/fuzz/transformation_replace_linear_algebra_instruction.h
@@ -26,7 +26,7 @@
 class TransformationReplaceLinearAlgebraInstruction : public Transformation {
  public:
   explicit TransformationReplaceLinearAlgebraInstruction(
-      const protobufs::TransformationReplaceLinearAlgebraInstruction& message);
+      protobufs::TransformationReplaceLinearAlgebraInstruction message);
 
   TransformationReplaceLinearAlgebraInstruction(
       const std::vector<uint32_t>& fresh_ids,
diff --git a/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp b/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp
index 6067fca..e75337f 100644
--- a/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp
+++ b/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp
@@ -29,9 +29,8 @@
 
 TransformationReplaceLoadStoreWithCopyMemory::
     TransformationReplaceLoadStoreWithCopyMemory(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceLoadStoreWithCopyMemory& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceLoadStoreWithCopyMemory message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceLoadStoreWithCopyMemory::
     TransformationReplaceLoadStoreWithCopyMemory(
diff --git a/source/fuzz/transformation_replace_load_store_with_copy_memory.h b/source/fuzz/transformation_replace_load_store_with_copy_memory.h
index 4dd728e..bb4d27e 100644
--- a/source/fuzz/transformation_replace_load_store_with_copy_memory.h
+++ b/source/fuzz/transformation_replace_load_store_with_copy_memory.h
@@ -26,7 +26,7 @@
 class TransformationReplaceLoadStoreWithCopyMemory : public Transformation {
  public:
   explicit TransformationReplaceLoadStoreWithCopyMemory(
-      const protobufs::TransformationReplaceLoadStoreWithCopyMemory& message);
+      protobufs::TransformationReplaceLoadStoreWithCopyMemory message);
 
   TransformationReplaceLoadStoreWithCopyMemory(
       const protobufs::InstructionDescriptor& load_instruction_descriptor,
diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp
index f13af7e..84ca1ab 100644
--- a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp
+++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp
@@ -21,9 +21,8 @@
 
 TransformationReplaceOpPhiIdFromDeadPredecessor::
     TransformationReplaceOpPhiIdFromDeadPredecessor(
-        const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor&
-            message)
-    : message_(message) {}
+        protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceOpPhiIdFromDeadPredecessor::
     TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id,
diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h
index d26b6b0..87ce8ad 100644
--- a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h
+++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h
@@ -23,8 +23,7 @@
 class TransformationReplaceOpPhiIdFromDeadPredecessor : public Transformation {
  public:
   explicit TransformationReplaceOpPhiIdFromDeadPredecessor(
-      const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor&
-          message);
+      protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor message);
 
   TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id,
                                                   uint32_t pred_label_id,
diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp
index 7160d4d..c0e6e44 100644
--- a/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp
+++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp
@@ -20,9 +20,8 @@
 namespace fuzz {
 TransformationReplaceOpSelectWithConditionalBranch::
     TransformationReplaceOpSelectWithConditionalBranch(
-        const spvtools::fuzz::protobufs::
-            TransformationReplaceOpSelectWithConditionalBranch& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceOpSelectWithConditionalBranch message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceOpSelectWithConditionalBranch::
     TransformationReplaceOpSelectWithConditionalBranch(
diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.h b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h
index 8ee5c7f..ec926c6 100644
--- a/source/fuzz/transformation_replace_opselect_with_conditional_branch.h
+++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h
@@ -24,8 +24,7 @@
     : public Transformation {
  public:
   explicit TransformationReplaceOpSelectWithConditionalBranch(
-      const protobufs::TransformationReplaceOpSelectWithConditionalBranch&
-          message);
+      protobufs::TransformationReplaceOpSelectWithConditionalBranch message);
 
   TransformationReplaceOpSelectWithConditionalBranch(uint32_t select_id,
                                                      uint32_t true_block_id,
diff --git a/source/fuzz/transformation_replace_parameter_with_global.cpp b/source/fuzz/transformation_replace_parameter_with_global.cpp
index cdf7645..caf6716 100644
--- a/source/fuzz/transformation_replace_parameter_with_global.cpp
+++ b/source/fuzz/transformation_replace_parameter_with_global.cpp
@@ -23,8 +23,8 @@
 
 TransformationReplaceParameterWithGlobal::
     TransformationReplaceParameterWithGlobal(
-        const protobufs::TransformationReplaceParameterWithGlobal& message)
-    : message_(message) {}
+        protobufs::TransformationReplaceParameterWithGlobal message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceParameterWithGlobal::
     TransformationReplaceParameterWithGlobal(
diff --git a/source/fuzz/transformation_replace_parameter_with_global.h b/source/fuzz/transformation_replace_parameter_with_global.h
index c2d5f8f..38a9c17 100644
--- a/source/fuzz/transformation_replace_parameter_with_global.h
+++ b/source/fuzz/transformation_replace_parameter_with_global.h
@@ -26,7 +26,7 @@
 class TransformationReplaceParameterWithGlobal : public Transformation {
  public:
   explicit TransformationReplaceParameterWithGlobal(
-      const protobufs::TransformationReplaceParameterWithGlobal& message);
+      protobufs::TransformationReplaceParameterWithGlobal message);
 
   TransformationReplaceParameterWithGlobal(uint32_t function_type_fresh_id,
                                            uint32_t parameter_id,
diff --git a/source/fuzz/transformation_replace_params_with_struct.cpp b/source/fuzz/transformation_replace_params_with_struct.cpp
index 0a135e5..13eeccb 100644
--- a/source/fuzz/transformation_replace_params_with_struct.cpp
+++ b/source/fuzz/transformation_replace_params_with_struct.cpp
@@ -22,8 +22,8 @@
 namespace fuzz {
 
 TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct(
-    const protobufs::TransformationReplaceParamsWithStruct& message)
-    : message_(message) {}
+    protobufs::TransformationReplaceParamsWithStruct message)
+    : message_(std::move(message)) {}
 
 TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct(
     const std::vector<uint32_t>& parameter_id, uint32_t fresh_function_type_id,
diff --git a/source/fuzz/transformation_replace_params_with_struct.h b/source/fuzz/transformation_replace_params_with_struct.h
index afa6b14..705f340 100644
--- a/source/fuzz/transformation_replace_params_with_struct.h
+++ b/source/fuzz/transformation_replace_params_with_struct.h
@@ -28,7 +28,7 @@
 class TransformationReplaceParamsWithStruct : public Transformation {
  public:
   explicit TransformationReplaceParamsWithStruct(
-      const protobufs::TransformationReplaceParamsWithStruct& message);
+      protobufs::TransformationReplaceParamsWithStruct message);
 
   TransformationReplaceParamsWithStruct(
       const std::vector<uint32_t>& parameter_id,
diff --git a/source/fuzz/transformation_set_function_control.cpp b/source/fuzz/transformation_set_function_control.cpp
index 8ab9b8c..02a8c9f 100644
--- a/source/fuzz/transformation_set_function_control.cpp
+++ b/source/fuzz/transformation_set_function_control.cpp
@@ -18,8 +18,8 @@
 namespace fuzz {
 
 TransformationSetFunctionControl::TransformationSetFunctionControl(
-    const spvtools::fuzz::protobufs::TransformationSetFunctionControl& message)
-    : message_(message) {}
+    protobufs::TransformationSetFunctionControl message)
+    : message_(std::move(message)) {}
 
 TransformationSetFunctionControl::TransformationSetFunctionControl(
     uint32_t function_id, uint32_t function_control) {
diff --git a/source/fuzz/transformation_set_function_control.h b/source/fuzz/transformation_set_function_control.h
index 2952cc6..2e16e1c 100644
--- a/source/fuzz/transformation_set_function_control.h
+++ b/source/fuzz/transformation_set_function_control.h
@@ -26,7 +26,7 @@
 class TransformationSetFunctionControl : public Transformation {
  public:
   explicit TransformationSetFunctionControl(
-      const protobufs::TransformationSetFunctionControl& message);
+      protobufs::TransformationSetFunctionControl message);
 
   TransformationSetFunctionControl(uint32_t function_id,
                                    uint32_t function_control);
diff --git a/source/fuzz/transformation_set_loop_control.cpp b/source/fuzz/transformation_set_loop_control.cpp
index b2180d8..1449960 100644
--- a/source/fuzz/transformation_set_loop_control.cpp
+++ b/source/fuzz/transformation_set_loop_control.cpp
@@ -18,8 +18,8 @@
 namespace fuzz {
 
 TransformationSetLoopControl::TransformationSetLoopControl(
-    const spvtools::fuzz::protobufs::TransformationSetLoopControl& message)
-    : message_(message) {}
+    protobufs::TransformationSetLoopControl message)
+    : message_(std::move(message)) {}
 
 TransformationSetLoopControl::TransformationSetLoopControl(
     uint32_t block_id, uint32_t loop_control, uint32_t peel_count,
@@ -77,12 +77,14 @@
     }
   }
 
-  if ((message_.loop_control() &
-       (SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask)) &&
-      !(PeelCountIsSupported(ir_context) &&
-        PartialCountIsSupported(ir_context))) {
-    // At least one of PeelCount or PartialCount is used, but the SPIR-V version
-    // in question does not support these loop controls.
+  // Check that PeelCount and PartialCount are supported if used.
+  if ((message_.loop_control() & SpvLoopControlPeelCountMask) &&
+      !PeelCountIsSupported(ir_context)) {
+    return false;
+  }
+
+  if ((message_.loop_control() & SpvLoopControlPartialCountMask) &&
+      !PartialCountIsSupported(ir_context)) {
     return false;
   }
 
@@ -183,14 +185,16 @@
 
 bool TransformationSetLoopControl::PartialCountIsSupported(
     opt::IRContext* ir_context) {
-  // TODO(afd): We capture the universal environments for which this loop
-  //  control is definitely not supported.  The check should be refined on
-  //  demand for other target environments.
+  // TODO(afd): We capture the environments for which this loop control is
+  //  definitely not supported.  The check should be refined on demand for other
+  //  target environments.
   switch (ir_context->grammar().target_env()) {
     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_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1:
       return false;
     default:
       return true;
@@ -199,14 +203,16 @@
 
 bool TransformationSetLoopControl::PeelCountIsSupported(
     opt::IRContext* ir_context) {
-  // TODO(afd): We capture the universal environments for which this loop
-  //  control is definitely not supported.  The check should be refined on
-  //  demand for other target environments.
+  // TODO(afd): We capture the environments for which this loop control is
+  //  definitely not supported.  The check should be refined on demand for other
+  //  target environments.
   switch (ir_context->grammar().target_env()) {
     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_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1:
       return false;
     default:
       return true;
diff --git a/source/fuzz/transformation_set_loop_control.h b/source/fuzz/transformation_set_loop_control.h
index c3480b1..bc17c8a 100644
--- a/source/fuzz/transformation_set_loop_control.h
+++ b/source/fuzz/transformation_set_loop_control.h
@@ -29,7 +29,7 @@
   const static uint32_t kLoopControlFirstLiteralInOperandIndex = 3;
 
   explicit TransformationSetLoopControl(
-      const protobufs::TransformationSetLoopControl& message);
+      protobufs::TransformationSetLoopControl message);
 
   TransformationSetLoopControl(uint32_t block_id, uint32_t loop_control,
                                uint32_t peel_count, uint32_t partial_count);
diff --git a/source/fuzz/transformation_set_memory_operands_mask.cpp b/source/fuzz/transformation_set_memory_operands_mask.cpp
index deb207a..5a986ad 100644
--- a/source/fuzz/transformation_set_memory_operands_mask.cpp
+++ b/source/fuzz/transformation_set_memory_operands_mask.cpp
@@ -29,9 +29,8 @@
 }  // namespace
 
 TransformationSetMemoryOperandsMask::TransformationSetMemoryOperandsMask(
-    const spvtools::fuzz::protobufs::TransformationSetMemoryOperandsMask&
-        message)
-    : message_(message) {}
+    protobufs::TransformationSetMemoryOperandsMask message)
+    : message_(std::move(message)) {}
 
 TransformationSetMemoryOperandsMask::TransformationSetMemoryOperandsMask(
     const protobufs::InstructionDescriptor& memory_access_instruction,
@@ -53,7 +52,9 @@
                SpvOpCopyMemory ||
            message_.memory_access_instruction().target_instruction_opcode() ==
                SpvOpCopyMemorySized);
-    assert(MultipleMemoryOperandMasksAreSupported(ir_context));
+    assert(MultipleMemoryOperandMasksAreSupported(ir_context) &&
+           "Multiple memory operand masks are not supported for this SPIR-V "
+           "version.");
   }
 
   auto instruction =
@@ -205,14 +206,16 @@
 
 bool TransformationSetMemoryOperandsMask::
     MultipleMemoryOperandMasksAreSupported(opt::IRContext* ir_context) {
-  // TODO(afd): We capture the universal environments for which this loop
-  //  control is definitely not supported.  The check should be refined on
-  //  demand for other target environments.
+  // TODO(afd): We capture the environments for which this loop control is
+  //  definitely not supported.  The check should be refined on demand for other
+  //  target environments.
   switch (ir_context->grammar().target_env()) {
     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_VULKAN_1_0:
+    case SPV_ENV_VULKAN_1_1:
       return false;
     default:
       return true;
diff --git a/source/fuzz/transformation_set_memory_operands_mask.h b/source/fuzz/transformation_set_memory_operands_mask.h
index 7357b1a..c52fbdb 100644
--- a/source/fuzz/transformation_set_memory_operands_mask.h
+++ b/source/fuzz/transformation_set_memory_operands_mask.h
@@ -26,7 +26,7 @@
 class TransformationSetMemoryOperandsMask : public Transformation {
  public:
   explicit TransformationSetMemoryOperandsMask(
-      const protobufs::TransformationSetMemoryOperandsMask& message);
+      protobufs::TransformationSetMemoryOperandsMask message);
 
   TransformationSetMemoryOperandsMask(
       const protobufs::InstructionDescriptor& memory_access_instruction,
diff --git a/source/fuzz/transformation_set_selection_control.cpp b/source/fuzz/transformation_set_selection_control.cpp
index 625187e..6dddbdf 100644
--- a/source/fuzz/transformation_set_selection_control.cpp
+++ b/source/fuzz/transformation_set_selection_control.cpp
@@ -18,8 +18,8 @@
 namespace fuzz {
 
 TransformationSetSelectionControl::TransformationSetSelectionControl(
-    const spvtools::fuzz::protobufs::TransformationSetSelectionControl& message)
-    : message_(message) {}
+    protobufs::TransformationSetSelectionControl message)
+    : message_(std::move(message)) {}
 
 TransformationSetSelectionControl::TransformationSetSelectionControl(
     uint32_t block_id, uint32_t selection_control) {
diff --git a/source/fuzz/transformation_set_selection_control.h b/source/fuzz/transformation_set_selection_control.h
index 56b5885..93b1904 100644
--- a/source/fuzz/transformation_set_selection_control.h
+++ b/source/fuzz/transformation_set_selection_control.h
@@ -26,7 +26,7 @@
 class TransformationSetSelectionControl : public Transformation {
  public:
   explicit TransformationSetSelectionControl(
-      const protobufs::TransformationSetSelectionControl& message);
+      protobufs::TransformationSetSelectionControl message);
 
   TransformationSetSelectionControl(uint32_t block_id,
                                     uint32_t selection_control);
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
index b383c40..e15dffa 100644
--- a/source/fuzz/transformation_split_block.cpp
+++ b/source/fuzz/transformation_split_block.cpp
@@ -24,8 +24,8 @@
 namespace fuzz {
 
 TransformationSplitBlock::TransformationSplitBlock(
-    const spvtools::fuzz::protobufs::TransformationSplitBlock& message)
-    : message_(message) {}
+    protobufs::TransformationSplitBlock message)
+    : message_(std::move(message)) {}
 
 TransformationSplitBlock::TransformationSplitBlock(
     const protobufs::InstructionDescriptor& instruction_to_split_before,
@@ -109,24 +109,37 @@
                                                 split_before);
   // The split does not automatically add a branch between the two parts of
   // the original block, so we add one.
-  block_to_split->AddInstruction(MakeUnique<opt::Instruction>(
+  auto branch_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpBranch, 0, 0,
       std::initializer_list<opt::Operand>{opt::Operand(
-          spv_operand_type_t::SPV_OPERAND_TYPE_ID, {message_.fresh_id()})}));
+          spv_operand_type_t::SPV_OPERAND_TYPE_ID, {message_.fresh_id()})});
+  auto branch_instruction_ptr = branch_instruction.get();
+  block_to_split->AddInstruction(std::move(branch_instruction));
+
+  // Inform the def-use manager about the branch instruction, and record its
+  // block.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(branch_instruction_ptr);
+  ir_context->set_instr_block(branch_instruction_ptr, block_to_split);
+
   // 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_to_split](opt::Instruction* phi_inst) {
+  new_bb->ForEachPhiInst([block_to_split,
+                          ir_context](opt::Instruction* phi_inst) {
     assert(
         phi_inst->NumInOperands() == 2 &&
         "Precondition: a block can only be split before an OpPhi if the block"
         "has exactly one predecessor.");
     phi_inst->SetInOperand(1, {block_to_split->id()});
+    ir_context->UpdateDefUse(phi_inst);
   });
 
-  // Invalidate all analyses
+  // We have updated the def-use manager and the instruction to block mapping,
+  // but other analyses (especially control flow-related ones) need to be
+  // recomputed.
   ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+      opt::IRContext::Analysis::kAnalysisDefUse |
+      opt::IRContext::Analysis::kAnalysisInstrToBlockMapping);
 
   // If the block being split was dead, the new block arising from the split is
   // also dead.
diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h
index 27bf6f8..ace77b5 100644
--- a/source/fuzz/transformation_split_block.h
+++ b/source/fuzz/transformation_split_block.h
@@ -26,7 +26,7 @@
 class TransformationSplitBlock : public Transformation {
  public:
   explicit TransformationSplitBlock(
-      const protobufs::TransformationSplitBlock& message);
+      protobufs::TransformationSplitBlock message);
 
   TransformationSplitBlock(
       const protobufs::InstructionDescriptor& instruction_to_split_before,
diff --git a/source/fuzz/transformation_store.cpp b/source/fuzz/transformation_store.cpp
index 460ca01..c00cd34 100644
--- a/source/fuzz/transformation_store.cpp
+++ b/source/fuzz/transformation_store.cpp
@@ -20,14 +20,17 @@
 namespace spvtools {
 namespace fuzz {
 
-TransformationStore::TransformationStore(
-    const spvtools::fuzz::protobufs::TransformationStore& message)
-    : message_(message) {}
+TransformationStore::TransformationStore(protobufs::TransformationStore message)
+    : message_(std::move(message)) {}
 
 TransformationStore::TransformationStore(
-    uint32_t pointer_id, uint32_t value_id,
+    uint32_t pointer_id, bool is_atomic, uint32_t memory_scope,
+    uint32_t memory_semantics, uint32_t value_id,
     const protobufs::InstructionDescriptor& instruction_to_insert_before) {
   message_.set_pointer_id(pointer_id);
+  message_.set_is_atomic(is_atomic);
+  message_.set_memory_scope_id(memory_scope);
+  message_.set_memory_semantics_id(memory_semantics);
   message_.set_value_id(value_id);
   *message_.mutable_instruction_to_insert_before() =
       instruction_to_insert_before;
@@ -71,8 +74,12 @@
     return false;
   }
   // ... and it must be legitimate to insert a store before it.
-  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore,
-                                                    insert_before)) {
+  if (!message_.is_atomic() && !fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                                   SpvOpStore, insert_before)) {
+    return false;
+  }
+  if (message_.is_atomic() && !fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                                  SpvOpAtomicStore, insert_before)) {
     return false;
   }
 
@@ -103,6 +110,87 @@
     return false;
   }
 
+  if (message_.is_atomic()) {
+    // Check the exists of memory scope and memory semantics ids.
+    auto memory_scope_instruction =
+        ir_context->get_def_use_mgr()->GetDef(message_.memory_scope_id());
+    auto memory_semantics_instruction =
+        ir_context->get_def_use_mgr()->GetDef(message_.memory_semantics_id());
+
+    if (!memory_scope_instruction) {
+      return false;
+    }
+    if (!memory_semantics_instruction) {
+      return false;
+    }
+    // The memory scope and memory semantics instructions must have the
+    // 'OpConstant' opcode.
+    if (memory_scope_instruction->opcode() != SpvOpConstant) {
+      return false;
+    }
+    if (memory_semantics_instruction->opcode() != SpvOpConstant) {
+      return false;
+    }
+    // The memory scope and memory semantics need to be available before
+    // |insert_before|.
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+            ir_context, insert_before, message_.memory_scope_id())) {
+      return false;
+    }
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+            ir_context, insert_before, message_.memory_semantics_id())) {
+      return false;
+    }
+    // The memory scope and memory semantics instructions must have an Integer
+    // operand type with signedness does not matters.
+    if (ir_context->get_def_use_mgr()
+            ->GetDef(memory_scope_instruction->type_id())
+            ->opcode() != SpvOpTypeInt) {
+      return false;
+    }
+    if (ir_context->get_def_use_mgr()
+            ->GetDef(memory_semantics_instruction->type_id())
+            ->opcode() != SpvOpTypeInt) {
+      return false;
+    }
+
+    // The size of the integer for memory scope and memory semantics
+    // instructions must be equal to 32 bits.
+    auto memory_scope_int_width =
+        ir_context->get_def_use_mgr()
+            ->GetDef(memory_scope_instruction->type_id())
+            ->GetSingleWordInOperand(0);
+    auto memory_semantics_int_width =
+        ir_context->get_def_use_mgr()
+            ->GetDef(memory_semantics_instruction->type_id())
+            ->GetSingleWordInOperand(0);
+
+    if (memory_scope_int_width != 32) {
+      return false;
+    }
+    if (memory_semantics_int_width != 32) {
+      return false;
+    }
+
+    // The memory scope constant value must be that of SpvScopeInvocation.
+    auto memory_scope_const_value =
+        memory_scope_instruction->GetSingleWordInOperand(0);
+    if (memory_scope_const_value != SpvScopeInvocation) {
+      return false;
+    }
+
+    // The memory semantics constant value must match the storage class of the
+    // pointer being loaded from.
+    auto memory_semantics_const_value = static_cast<SpvMemorySemanticsMask>(
+        memory_semantics_instruction->GetSingleWordInOperand(0));
+    if (memory_semantics_const_value !=
+        fuzzerutil::GetMemorySemanticsForStorageClass(
+            static_cast<SpvStorageClass>(
+                pointer_type->GetSingleWordInOperand(0)))) {
+      return false;
+    }
+  }
+
   // The value needs to be available at the insertion point.
   return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
                                                     message_.value_id());
@@ -110,13 +198,43 @@
 
 void TransformationStore::Apply(opt::IRContext* ir_context,
                                 TransformationContext* /*unused*/) const {
-  FindInstruction(message_.instruction_to_insert_before(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
-          ir_context, SpvOpStore, 0, 0,
-          opt::Instruction::OperandList(
-              {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}},
-               {SPV_OPERAND_TYPE_ID, {message_.value_id()}}})));
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+  if (message_.is_atomic()) {
+    // OpAtomicStore instruction.
+    auto insert_before =
+        FindInstruction(message_.instruction_to_insert_before(), ir_context);
+    auto new_instruction = MakeUnique<opt::Instruction>(
+        ir_context, SpvOpAtomicStore, 0, 0,
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}},
+             {SPV_OPERAND_TYPE_SCOPE_ID, {message_.memory_scope_id()}},
+             {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID,
+              {message_.memory_semantics_id()}},
+             {SPV_OPERAND_TYPE_ID, {message_.value_id()}}}));
+    auto new_instruction_ptr = new_instruction.get();
+    insert_before->InsertBefore(std::move(new_instruction));
+    // Inform the def-use manager about the new instruction and record its basic
+    // block.
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+    ir_context->set_instr_block(new_instruction_ptr,
+                                ir_context->get_instr_block(insert_before));
+
+  } else {
+    // OpStore instruction.
+    auto insert_before =
+        FindInstruction(message_.instruction_to_insert_before(), ir_context);
+    auto new_instruction = MakeUnique<opt::Instruction>(
+        ir_context, SpvOpStore, 0, 0,
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}},
+             {SPV_OPERAND_TYPE_ID, {message_.value_id()}}}));
+    auto new_instruction_ptr = new_instruction.get();
+    insert_before->InsertBefore(std::move(new_instruction));
+    // Inform the def-use manager about the new instruction and record its basic
+    // block.
+    ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+    ir_context->set_instr_block(new_instruction_ptr,
+                                ir_context->get_instr_block(insert_before));
+  }
 }
 
 protobufs::Transformation TransformationStore::ToMessage() const {
diff --git a/source/fuzz/transformation_store.h b/source/fuzz/transformation_store.h
index 7052048..638713b 100644
--- a/source/fuzz/transformation_store.h
+++ b/source/fuzz/transformation_store.h
@@ -25,15 +25,24 @@
 
 class TransformationStore : public Transformation {
  public:
-  explicit TransformationStore(const protobufs::TransformationStore& message);
+  explicit TransformationStore(protobufs::TransformationStore message);
 
   TransformationStore(
-      uint32_t pointer_id, uint32_t value_id,
+      uint32_t pointer_id, bool is_atomic, uint32_t memory_scope,
+      uint32_t memory_semantics, uint32_t value_id,
       const protobufs::InstructionDescriptor& instruction_to_insert_before);
 
   // - |message_.pointer_id| must be the id of a pointer
   // - The pointer type must not have read-only storage class
   // - The pointer must not be OpConstantNull or OpUndef
+  // - |message_.is_atomic| must be true if want to work with OpAtomicStore.
+  // - If |is_atomic| is true then |message_memory_scope_id| must be the id of
+  //   an OpConstant 32 bit integer instruction with the value
+  //   SpvScopeInvocation.
+  // - If |is_atomic| is true then |message_.memory_semantics_id| must be the id
+  //   of an OpConstant 32 bit integer instruction with the values
+  //   SpvMemorySemanticsWorkgroupMemoryMask or
+  //   SpvMemorySemanticsUniformMemoryMask.
   // - |message_.value_id| must be an instruction result id that has the same
   //   type as the pointee type of |message_.pointer_id|
   // - |message_.instruction_to_insert_before| must identify an instruction
diff --git a/source/fuzz/transformation_swap_commutable_operands.cpp b/source/fuzz/transformation_swap_commutable_operands.cpp
index b8bdb79..a02e95a 100644
--- a/source/fuzz/transformation_swap_commutable_operands.cpp
+++ b/source/fuzz/transformation_swap_commutable_operands.cpp
@@ -21,9 +21,8 @@
 namespace fuzz {
 
 TransformationSwapCommutableOperands::TransformationSwapCommutableOperands(
-    const spvtools::fuzz::protobufs::TransformationSwapCommutableOperands&
-        message)
-    : message_(message) {}
+    protobufs::TransformationSwapCommutableOperands message)
+    : message_(std::move(message)) {}
 
 TransformationSwapCommutableOperands::TransformationSwapCommutableOperands(
     const protobufs::InstructionDescriptor& instruction_descriptor) {
diff --git a/source/fuzz/transformation_swap_commutable_operands.h b/source/fuzz/transformation_swap_commutable_operands.h
index c291c3e..5e211f1 100644
--- a/source/fuzz/transformation_swap_commutable_operands.h
+++ b/source/fuzz/transformation_swap_commutable_operands.h
@@ -26,7 +26,7 @@
 class TransformationSwapCommutableOperands : public Transformation {
  public:
   explicit TransformationSwapCommutableOperands(
-      const protobufs::TransformationSwapCommutableOperands& message);
+      protobufs::TransformationSwapCommutableOperands message);
 
   TransformationSwapCommutableOperands(
       const protobufs::InstructionDescriptor& instruction_descriptor);
diff --git a/source/fuzz/transformation_swap_conditional_branch_operands.cpp b/source/fuzz/transformation_swap_conditional_branch_operands.cpp
index 866fee6..340836d 100644
--- a/source/fuzz/transformation_swap_conditional_branch_operands.cpp
+++ b/source/fuzz/transformation_swap_conditional_branch_operands.cpp
@@ -22,9 +22,8 @@
 
 TransformationSwapConditionalBranchOperands::
     TransformationSwapConditionalBranchOperands(
-        const spvtools::fuzz::protobufs::
-            TransformationSwapConditionalBranchOperands& message)
-    : message_(message) {}
+        protobufs::TransformationSwapConditionalBranchOperands message)
+    : message_(std::move(message)) {}
 
 TransformationSwapConditionalBranchOperands::
     TransformationSwapConditionalBranchOperands(
@@ -70,11 +69,13 @@
 
   // We are swapping the labels in OpBranchConditional. This means that we must
   // invert the guard as well. We are using OpLogicalNot for that purpose here.
-  iter.InsertBefore(MakeUnique<opt::Instruction>(
+  auto new_instruction = MakeUnique<opt::Instruction>(
       ir_context, SpvOpLogicalNot, condition_inst->type_id(),
       message_.fresh_id(),
       opt::Instruction::OperandList{
-          {SPV_OPERAND_TYPE_ID, {condition_inst->result_id()}}}));
+          {SPV_OPERAND_TYPE_ID, {condition_inst->result_id()}}});
+  auto new_instruction_ptr = new_instruction.get();
+  iter.InsertBefore(std::move(new_instruction));
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
@@ -89,9 +90,13 @@
     std::swap(branch_inst->GetInOperand(3), branch_inst->GetInOperand(4));
   }
 
-  // Make sure the changes are analyzed.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+  ir_context->set_instr_block(new_instruction_ptr, block);
+  ir_context->get_def_use_mgr()->EraseUseRecordsOfOperandIds(branch_inst);
+  ir_context->get_def_use_mgr()->AnalyzeInstUse(branch_inst);
+
+  // No analyses need to be invalidated since the transformation is local to a
+  // block and the def-use and instruction-to-block mappings have been updated.
 }
 
 protobufs::Transformation
diff --git a/source/fuzz/transformation_swap_conditional_branch_operands.h b/source/fuzz/transformation_swap_conditional_branch_operands.h
index 022c54a..165ab80 100644
--- a/source/fuzz/transformation_swap_conditional_branch_operands.h
+++ b/source/fuzz/transformation_swap_conditional_branch_operands.h
@@ -26,7 +26,7 @@
 class TransformationSwapConditionalBranchOperands : public Transformation {
  public:
   explicit TransformationSwapConditionalBranchOperands(
-      const protobufs::TransformationSwapConditionalBranchOperands& message);
+      protobufs::TransformationSwapConditionalBranchOperands message);
 
   TransformationSwapConditionalBranchOperands(
       const protobufs::InstructionDescriptor& instruction_descriptor,
diff --git a/source/fuzz/transformation_swap_function_variables.cpp b/source/fuzz/transformation_swap_function_variables.cpp
new file mode 100644
index 0000000..aec32fe
--- /dev/null
+++ b/source/fuzz/transformation_swap_function_variables.cpp
@@ -0,0 +1,93 @@
+// Copyright (c) 2021 Mostafa Ashraf
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_swap_function_variables.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSwapFunctionVariables::TransformationSwapFunctionVariables(
+    protobufs::TransformationSwapFunctionVariables message)
+    : message_(std::move(message)) {}
+
+TransformationSwapFunctionVariables::TransformationSwapFunctionVariables(
+    uint32_t result_id1, uint32_t result_id2) {
+  message_.set_result_id1(result_id1);
+  message_.set_result_id2(result_id2);
+}
+
+bool TransformationSwapFunctionVariables::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  uint32_t result_id1 = message_.result_id1();
+  uint32_t result_id2 = message_.result_id2();
+
+  assert((result_id1 != result_id2) && "Two results ids are equal");
+
+  // The result ids used in the message must refer to instructions.
+  auto instruction1 = ir_context->get_def_use_mgr()->GetDef(result_id1);
+  auto instruction2 = ir_context->get_def_use_mgr()->GetDef(result_id2);
+  if (instruction1 == nullptr || instruction2 == nullptr) {
+    return false;
+  }
+  // Both instructions must be variables.
+  if (instruction1->opcode() != SpvOpVariable ||
+      instruction2->opcode() != SpvOpVariable) {
+    return false;
+  }
+
+  // Both variable instructions must be in some basic block (as they are
+  // function-local variables), and they must be in the same block (as they need
+  // to be variables of the same function).
+  auto* block_1 = ir_context->get_instr_block(result_id1);
+  auto* block_2 = ir_context->get_instr_block(result_id2);
+  if (block_1 == nullptr || block_2 == nullptr) {
+    return false;
+  }
+
+  return block_1 == block_2;
+}
+
+void TransformationSwapFunctionVariables::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // The result ids used in the message must refer to instructions.
+  auto instruction1 =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id1());
+  auto instruction2 =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id2());
+
+  std::unique_ptr<opt::Instruction> temp_instruction =
+      MakeUnique<opt::Instruction>();
+
+  temp_instruction->InsertBefore(instruction1);
+  instruction1->InsertAfter(instruction2);
+  instruction2->InsertAfter(temp_instruction.get());
+  temp_instruction->RemoveFromList();
+}
+
+protobufs::Transformation TransformationSwapFunctionVariables::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_swap_function_variables() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t> TransformationSwapFunctionVariables::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_swap_function_variables.h b/source/fuzz/transformation_swap_function_variables.h
new file mode 100644
index 0000000..eb383f4
--- /dev/null
+++ b/source/fuzz/transformation_swap_function_variables.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2021 Mostafa Ashraf
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_SWAP_FUNCTION_VARIABLES_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SWAP_FUNCTION_VARIABLES_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A transformation that swaps two variable declaration instructions that appear
+// in the same function.
+class TransformationSwapFunctionVariables : public Transformation {
+ public:
+  explicit TransformationSwapFunctionVariables(
+      protobufs::TransformationSwapFunctionVariables message);
+
+  TransformationSwapFunctionVariables(uint32_t result_id1, uint32_t result_id2);
+
+  // - |message_.result_id1| and |message_.result_id2| must be the ids of
+  //   distinct OpVariable instructions appearing in the same function.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Swaps two OpVariable instructions with result ids |message_.result_id1|
+  // and |message_.result_id2|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+ private:
+  protobufs::TransformationSwapFunctionVariables message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SWAP_FUNCTION_VARIABLES_H_
diff --git a/source/fuzz/transformation_swap_two_functions.cpp b/source/fuzz/transformation_swap_two_functions.cpp
new file mode 100644
index 0000000..85d9e79
--- /dev/null
+++ b/source/fuzz/transformation_swap_two_functions.cpp
@@ -0,0 +1,72 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_swap_two_functions.h"
+
+#include "source/opt/function.h"
+#include "source/opt/module.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationSwapTwoFunctions::TransformationSwapTwoFunctions(
+    protobufs::TransformationSwapTwoFunctions message)
+    : message_(std::move(message)) {}
+
+TransformationSwapTwoFunctions::TransformationSwapTwoFunctions(uint32_t id1,
+                                                               uint32_t id2) {
+  assert(id1 != id2 && "The two function ids cannot be the same.");
+  message_.set_function_id1(id1);
+  message_.set_function_id2(id2);
+}
+
+bool TransformationSwapTwoFunctions::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  auto func1_ptr = ir_context->GetFunction(message_.function_id1());
+  auto func2_ptr = ir_context->GetFunction(message_.function_id2());
+  return func1_ptr != nullptr && func2_ptr != nullptr;
+}
+
+void TransformationSwapTwoFunctions::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  opt::Module::iterator func1_it =
+      fuzzerutil::GetFunctionIterator(ir_context, message_.function_id1());
+  opt::Module::iterator func2_it =
+      fuzzerutil::GetFunctionIterator(ir_context, message_.function_id2());
+
+  assert(func1_it != ir_context->module()->end() &&
+         "Could not find function 1.");
+  assert(func2_it != ir_context->module()->end() &&
+         "Could not find function 2.");
+
+  // Two function pointers are all set, swap the two functions within the
+  // module.
+  std::iter_swap(func1_it.Get(), func2_it.Get());
+}
+
+protobufs::Transformation TransformationSwapTwoFunctions::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_swap_two_functions() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t> TransformationSwapTwoFunctions::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_swap_two_functions.h b/source/fuzz/transformation_swap_two_functions.h
new file mode 100644
index 0000000..1b6d6e8
--- /dev/null
+++ b/source/fuzz/transformation_swap_two_functions.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_SWAP_TWO_FUNCTIONS_H_
+#define SOURCE_FUZZ_TRANSFORMATION_SWAP_TWO_FUNCTIONS_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationSwapTwoFunctions : public Transformation {
+ public:
+  explicit TransformationSwapTwoFunctions(
+      protobufs::TransformationSwapTwoFunctions message);
+
+  TransformationSwapTwoFunctions(uint32_t function_id1, uint32_t function_id2);
+
+  // |function_id1| and  |function_id1| should all be existing ids.
+  //  Swap function operation is only permitted if:
+  //  - both ids must be ids of functions.
+  //  - both ids can be found in the module.
+  //  - function_id1 and function_id2 are not the same.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // OpFunction with |function_id1| and |function_id1| are swapped.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationSwapTwoFunctions message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_SWAP_TWO_FUNCTIONS_H_
diff --git a/source/fuzz/transformation_toggle_access_chain_instruction.cpp b/source/fuzz/transformation_toggle_access_chain_instruction.cpp
index 1952a34..34523fe 100644
--- a/source/fuzz/transformation_toggle_access_chain_instruction.cpp
+++ b/source/fuzz/transformation_toggle_access_chain_instruction.cpp
@@ -22,9 +22,8 @@
 
 TransformationToggleAccessChainInstruction::
     TransformationToggleAccessChainInstruction(
-        const spvtools::fuzz::protobufs::
-            TransformationToggleAccessChainInstruction& message)
-    : message_(message) {}
+        protobufs::TransformationToggleAccessChainInstruction message)
+    : message_(std::move(message)) {}
 
 TransformationToggleAccessChainInstruction::
     TransformationToggleAccessChainInstruction(
diff --git a/source/fuzz/transformation_toggle_access_chain_instruction.h b/source/fuzz/transformation_toggle_access_chain_instruction.h
index 977b0d7..be2718b 100644
--- a/source/fuzz/transformation_toggle_access_chain_instruction.h
+++ b/source/fuzz/transformation_toggle_access_chain_instruction.h
@@ -26,7 +26,7 @@
 class TransformationToggleAccessChainInstruction : public Transformation {
  public:
   explicit TransformationToggleAccessChainInstruction(
-      const protobufs::TransformationToggleAccessChainInstruction& message);
+      protobufs::TransformationToggleAccessChainInstruction message);
 
   TransformationToggleAccessChainInstruction(
       const protobufs::InstructionDescriptor& instruction_descriptor);
diff --git a/source/fuzz/transformation_vector_shuffle.cpp b/source/fuzz/transformation_vector_shuffle.cpp
index 05af18e..742a2c8 100644
--- a/source/fuzz/transformation_vector_shuffle.cpp
+++ b/source/fuzz/transformation_vector_shuffle.cpp
@@ -21,8 +21,8 @@
 namespace fuzz {
 
 TransformationVectorShuffle::TransformationVectorShuffle(
-    const spvtools::fuzz::protobufs::TransformationVectorShuffle& message)
-    : message_(message) {}
+    protobufs::TransformationVectorShuffle message)
+    : message_(std::move(message)) {}
 
 TransformationVectorShuffle::TransformationVectorShuffle(
     const protobufs::InstructionDescriptor& instruction_to_insert_before,
@@ -130,13 +130,18 @@
 
   // Add a shuffle instruction right before the instruction identified by
   // |message_.instruction_to_insert_before|.
-  FindInstruction(message_.instruction_to_insert_before(), ir_context)
-      ->InsertBefore(MakeUnique<opt::Instruction>(
+  auto insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  opt::Instruction* new_instruction =
+      insert_before->InsertBefore(MakeUnique<opt::Instruction>(
           ir_context, SpvOpVectorShuffle, result_type_id, message_.fresh_id(),
           shuffle_operands));
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
+  // Inform the def-use manager about the new instruction and record its basic
+  // block.
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction);
+  ir_context->set_instr_block(new_instruction,
+                              ir_context->get_instr_block(insert_before));
 
   AddDataSynonymFacts(ir_context, transformation_context);
 }
@@ -199,7 +204,7 @@
       // Check that the first vector can participate in data synonym facts.
       if (!fuzzerutil::CanMakeSynonymOf(
               ir_context, *transformation_context,
-              ir_context->get_def_use_mgr()->GetDef(message_.vector1()))) {
+              *ir_context->get_def_use_mgr()->GetDef(message_.vector1()))) {
         continue;
       }
       descriptor_for_source_component =
@@ -208,7 +213,7 @@
       // Check that the second vector can participate in data synonym facts.
       if (!fuzzerutil::CanMakeSynonymOf(
               ir_context, *transformation_context,
-              ir_context->get_def_use_mgr()->GetDef(message_.vector2()))) {
+              *ir_context->get_def_use_mgr()->GetDef(message_.vector2()))) {
         continue;
       }
       auto index_into_vector_2 =
diff --git a/source/fuzz/transformation_vector_shuffle.h b/source/fuzz/transformation_vector_shuffle.h
index cf08a62..7360906 100644
--- a/source/fuzz/transformation_vector_shuffle.h
+++ b/source/fuzz/transformation_vector_shuffle.h
@@ -27,7 +27,7 @@
 class TransformationVectorShuffle : public Transformation {
  public:
   explicit TransformationVectorShuffle(
-      const protobufs::TransformationVectorShuffle& message);
+      protobufs::TransformationVectorShuffle message);
 
   TransformationVectorShuffle(
       const protobufs::InstructionDescriptor& instruction_to_insert_before,
diff --git a/source/fuzz/transformation_wrap_early_terminator_in_function.cpp b/source/fuzz/transformation_wrap_early_terminator_in_function.cpp
index 4c436f5..468d809 100644
--- a/source/fuzz/transformation_wrap_early_terminator_in_function.cpp
+++ b/source/fuzz/transformation_wrap_early_terminator_in_function.cpp
@@ -23,9 +23,8 @@
 
 TransformationWrapEarlyTerminatorInFunction::
     TransformationWrapEarlyTerminatorInFunction(
-        const spvtools::fuzz::protobufs::
-            TransformationWrapEarlyTerminatorInFunction& message)
-    : message_(message) {}
+        protobufs::TransformationWrapEarlyTerminatorInFunction message)
+    : message_(std::move(message)) {}
 
 TransformationWrapEarlyTerminatorInFunction::
     TransformationWrapEarlyTerminatorInFunction(
diff --git a/source/fuzz/transformation_wrap_early_terminator_in_function.h b/source/fuzz/transformation_wrap_early_terminator_in_function.h
index 00151d4..d6e5551 100644
--- a/source/fuzz/transformation_wrap_early_terminator_in_function.h
+++ b/source/fuzz/transformation_wrap_early_terminator_in_function.h
@@ -26,7 +26,7 @@
 class TransformationWrapEarlyTerminatorInFunction : public Transformation {
  public:
   explicit TransformationWrapEarlyTerminatorInFunction(
-      const protobufs::TransformationWrapEarlyTerminatorInFunction& message);
+      protobufs::TransformationWrapEarlyTerminatorInFunction message);
 
   TransformationWrapEarlyTerminatorInFunction(
       uint32_t fresh_id,
diff --git a/source/fuzz/transformation_wrap_region_in_selection.cpp b/source/fuzz/transformation_wrap_region_in_selection.cpp
index 9924e36..01c98cc 100644
--- a/source/fuzz/transformation_wrap_region_in_selection.cpp
+++ b/source/fuzz/transformation_wrap_region_in_selection.cpp
@@ -20,8 +20,8 @@
 namespace fuzz {
 
 TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
-    const protobufs::TransformationWrapRegionInSelection& message)
-    : message_(message) {}
+    protobufs::TransformationWrapRegionInSelection message)
+    : message_(std::move(message)) {}
 
 TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
     uint32_t region_entry_block_id, uint32_t region_exit_block_id,
diff --git a/source/fuzz/transformation_wrap_region_in_selection.h b/source/fuzz/transformation_wrap_region_in_selection.h
index 57f4f64..66d16da 100644
--- a/source/fuzz/transformation_wrap_region_in_selection.h
+++ b/source/fuzz/transformation_wrap_region_in_selection.h
@@ -26,7 +26,7 @@
 class TransformationWrapRegionInSelection : public Transformation {
  public:
   explicit TransformationWrapRegionInSelection(
-      const protobufs::TransformationWrapRegionInSelection& message);
+      protobufs::TransformationWrapRegionInSelection message);
 
   TransformationWrapRegionInSelection(uint32_t region_entry_block_id,
                                       uint32_t region_exit_block_id,
diff --git a/source/fuzz/transformation_wrap_vector_synonym.cpp b/source/fuzz/transformation_wrap_vector_synonym.cpp
new file mode 100644
index 0000000..490bcd7
--- /dev/null
+++ b/source/fuzz/transformation_wrap_vector_synonym.cpp
@@ -0,0 +1,200 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_wrap_vector_synonym.h"
+
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationWrapVectorSynonym::TransformationWrapVectorSynonym(
+    protobufs::TransformationWrapVectorSynonym message)
+    : message_(std::move(message)) {}
+
+TransformationWrapVectorSynonym::TransformationWrapVectorSynonym(
+    uint32_t instruction_id, uint32_t vector_operand1, uint32_t vector_operand2,
+    uint32_t fresh_id, uint32_t pos) {
+  message_.set_instruction_id(instruction_id);
+  message_.set_vector_operand1(vector_operand1);
+  message_.set_vector_operand2(vector_operand2);
+  message_.set_fresh_id(fresh_id);
+  message_.set_scalar_position(pos);
+}
+
+bool TransformationWrapVectorSynonym::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // |fresh_id| must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  const opt::Instruction* instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_id());
+
+  // |instruction_id| must refer to an existing instruction.
+  if (instruction == nullptr) {
+    return false;
+  }
+
+  if (!IsInstructionSupported(ir_context, *instruction)) {
+    return false;
+  }
+
+  // It must be possible to make a synonym of the result id of the scalar
+  // operation
+  if (!fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
+                                    *instruction)) {
+    return false;
+  }
+
+  // |vector_operand1| and |vector_operand2| must exist.
+  auto vec1 = ir_context->get_def_use_mgr()->GetDef(message_.vector_operand1());
+  auto vec2 = ir_context->get_def_use_mgr()->GetDef(message_.vector_operand2());
+
+  if (vec1 == nullptr || vec2 == nullptr) {
+    return false;
+  }
+
+  // The 2 vectors must have compatible vector types.
+  auto vec1_type_id = vec1->type_id();
+  auto vec2_type_id = vec2->type_id();
+
+  for (auto operand_index : {0, 1}) {
+    if (!fuzzerutil::TypesAreCompatible(ir_context, instruction->opcode(),
+                                        operand_index, vec1_type_id,
+                                        vec2_type_id)) {
+      return false;
+    }
+  }
+
+  auto vec1_type = ir_context->get_def_use_mgr()->GetDef(vec1_type_id);
+  if (vec1_type->opcode() != SpvOpTypeVector) {
+    return false;
+  }
+
+  // A suitable vector for the result type of the new vector instruction must
+  // exist in the module. This is a vector of the right length, whose element
+  // type matches the result type of the scalar instruction.
+  uint32_t vector_size = vec1_type->GetSingleWordInOperand(1);
+  if (!fuzzerutil::MaybeGetVectorType(ir_context, instruction->type_id(),
+                                      vector_size)) {
+    return false;
+  }
+
+  // |scalar_position| needs to be a non-negative integer less than the vector
+  // length.
+  // OpTypeVector instruction has the component count at index 2.
+  if (message_.scalar_position() >= ir_context->get_def_use_mgr()
+                                        ->GetDef(vec1_type_id)
+                                        ->GetSingleWordInOperand(1)) {
+    return false;
+  }
+
+  if (!transformation_context.GetFactManager()->IsSynonymous(
+          MakeDataDescriptor(message_.vector_operand1(),
+                             {message_.scalar_position()}),
+          MakeDataDescriptor(instruction->GetSingleWordInOperand(0), {}))) {
+    return false;
+  }
+
+  if (!transformation_context.GetFactManager()->IsSynonymous(
+          MakeDataDescriptor(message_.vector_operand2(),
+                             {message_.scalar_position()}),
+          MakeDataDescriptor(instruction->GetSingleWordInOperand(1), {}))) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationWrapVectorSynonym::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Create an instruction descriptor for the original instruction.
+  auto instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_id());
+  auto destination_block = ir_context->get_instr_block(instruction);
+
+  //  Populate input operand list with two vectors for vector operation.
+  opt::Instruction::OperandList in_operands;
+  in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.vector_operand1()}});
+  in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.vector_operand2()}});
+
+  // Make a new arithmetic instruction: %fresh_id = OpXX %type_id %result_id1
+  // %result_id2.
+  auto vector_operand_type = ir_context->get_def_use_mgr()->GetDef(
+      fuzzerutil::GetTypeId(ir_context, message_.vector_operand1()));
+  uint32_t vector_size = vector_operand_type->GetSingleWordInOperand(1);
+  auto vec_type_id = fuzzerutil::MaybeGetVectorType(
+      ir_context, instruction->type_id(), vector_size);
+  auto new_instruction = MakeUnique<opt::Instruction>(
+      ir_context, instruction->opcode(), vec_type_id, message_.fresh_id(),
+      std::move(in_operands));
+  auto new_instruction_ptr = new_instruction.get();
+  instruction->InsertBefore(std::move(new_instruction));
+  ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr);
+  ir_context->set_instr_block(new_instruction_ptr, destination_block);
+
+  // Add |fresh_id| to id bound.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Add synonyms between |fresh_id| and |instruction_id|.
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(message_.fresh_id(), {message_.scalar_position()}),
+      MakeDataDescriptor(message_.instruction_id(), {}));
+}
+
+protobufs::Transformation TransformationWrapVectorSynonym::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_wrap_vector_synonym() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t> TransformationWrapVectorSynonym::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>{message_.fresh_id()};
+}
+
+bool TransformationWrapVectorSynonym::IsInstructionSupported(
+    opt::IRContext* ir_context, const opt::Instruction& instruction) {
+  if (!instruction.result_id() || !instruction.type_id()) {
+    return false;
+  }
+  auto type_instruction =
+      ir_context->get_def_use_mgr()->GetDef(instruction.type_id());
+
+  if ((type_instruction->opcode() != SpvOpTypeInt &&
+       type_instruction->opcode() != SpvOpTypeFloat)) {
+    return false;
+  }
+
+  switch (instruction.opcode()) {
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+    case SpvOpFAdd:
+    case SpvOpFSub:
+    case SpvOpFMul:
+      return true;
+    default:
+      return false;
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_wrap_vector_synonym.h b/source/fuzz/transformation_wrap_vector_synonym.h
new file mode 100644
index 0000000..94437fe
--- /dev/null
+++ b/source/fuzz/transformation_wrap_vector_synonym.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_WRAP_VECTOR_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_WRAP_VECTOR_SYNONYM_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationWrapVectorSynonym : public Transformation {
+ public:
+  explicit TransformationWrapVectorSynonym(
+      protobufs::TransformationWrapVectorSynonym message);
+
+  TransformationWrapVectorSynonym(uint32_t instruction_id,
+                                  uint32_t vector_operand1,
+                                  uint32_t vector_operand2, uint32_t fresh_id,
+                                  uint32_t pos);
+  // - |instruction_id| must be the id of a supported arithmetic operation
+  //   and must be relevant.
+  // - |vector_operand1| and |vector_operand2| represents the result ids of the
+  //   two vector operands.
+  // - |fresh_id| is an unused id that will be used as a result id of the
+  //   created instruction.
+  // - |vector_operand1| and |vector_operand2| must have compatible vector types
+  //   that are supported by this transformation.
+  // - |pos| is an index of the operands of |instruction_id| in the
+  //   |vector_operand1| and |vector_operand2|. It must be less than the size
+  //   of those vector operands.
+  // - A vector type with the same width as the types of the vector operands,
+  //   and element type matching the type of |instruction_id|, must exist in the
+  //   module.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds a new instruction before the |instruction_id| with |fresh_id|
+  // result id and |instruction_id|'s opcode. The added instruction has
+  // two operands: |vector_operand1| and |vector_operand2| and its type
+  // id is equal to the type ids of those operands. A new fact is added
+  // to the fact manager specifying that |fresh_id[pos]| is synonymous
+  // to |instruction_id|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+  protobufs::Transformation ToMessage() const override;
+
+  // Checks whether the instruction given is supported by the transformation.
+  // A valid instruction must:
+  // - has both result id and type id.
+  // - is a supported scalar operation instruction.
+  // - has a supported type that is either int or float.
+  static bool IsInstructionSupported(opt::IRContext* ir_context,
+                                     const opt::Instruction& instruction);
+
+ private:
+  protobufs::TransformationWrapVectorSynonym message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_WRAP_VECTOR_SYNONYM_H_
diff --git a/source/link/linker.cpp b/source/link/linker.cpp
index 8da4a98..c5ca562 100644
--- a/source/link/linker.cpp
+++ b/source/link/linker.cpp
@@ -34,6 +34,7 @@
 #include "source/opt/pass_manager.h"
 #include "source/opt/remove_duplicates_pass.h"
 #include "source/opt/type_manager.h"
+#include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/util/make_unique.h"
 #include "spirv-tools/libspirv.hpp"
@@ -207,7 +208,7 @@
 
   header->magic_number = SpvMagicNumber;
   header->version = version;
-  header->generator = 17u;
+  header->generator = SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_LINKER, 0);
   header->bound = max_id_bound;
   header->reserved = 0u;
 
diff --git a/source/lint/CMakeLists.txt b/source/lint/CMakeLists.txt
new file mode 100644
index 0000000..1feae3f
--- /dev/null
+++ b/source/lint/CMakeLists.txt
@@ -0,0 +1,61 @@
+# Copyright (c) 2021 Google LLC.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+set(SPIRV_TOOLS_LINT_SOURCES
+  divergence_analysis.h
+  lints.h
+
+  linter.cpp
+  divergence_analysis.cpp
+  lint_divergent_derivatives.cpp
+)
+
+if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
+  # Enable parallel builds across four cores for this lib.
+  add_definitions(/MP4)
+endif()
+
+add_library(SPIRV-Tools-lint ${SPIRV_TOOLS_LIBRARY_TYPE} ${SPIRV_TOOLS_LINT_SOURCES})
+
+spvtools_default_compile_options(SPIRV-Tools-lint)
+target_include_directories(SPIRV-Tools-lint
+  PUBLIC
+	$<BUILD_INTERFACE:${spirv-tools_SOURCE_DIR}/include>
+	$<BUILD_INTERFACE:${SPIRV_HEADER_INCLUDE_DIR}>
+	$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+  PRIVATE ${spirv-tools_BINARY_DIR}
+)
+# We need the assembling and disassembling functionalities in the main library.
+target_link_libraries(SPIRV-Tools-lint
+  PUBLIC ${SPIRV_TOOLS_FULL_VISIBILITY})
+# We need the internals of spirv-opt.
+target_link_libraries(SPIRV-Tools-lint
+  PUBLIC SPIRV-Tools-opt)
+
+set_property(TARGET SPIRV-Tools-lint PROPERTY FOLDER "SPIRV-Tools libraries")
+spvtools_check_symbol_exports(SPIRV-Tools-lint)
+
+if(ENABLE_SPIRV_TOOLS_INSTALL)
+  install(TARGETS SPIRV-Tools-lint EXPORT SPIRV-Tools-lintTargets
+    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+  export(EXPORT SPIRV-Tools-lintTargets FILE SPIRV-Tools-lintTargets.cmake)
+
+  spvtools_config_package_dir(SPIRV-Tools-lint PACKAGE_DIR)
+  install(EXPORT SPIRV-Tools-lintTargets FILE SPIRV-Tools-lintTargets.cmake
+  	DESTINATION ${PACKAGE_DIR})
+
+  spvtools_generate_config_file(SPIRV-Tools-lint)
+  install(FILES ${CMAKE_BINARY_DIR}/SPIRV-Tools-lintConfig.cmake DESTINATION ${PACKAGE_DIR})
+endif(ENABLE_SPIRV_TOOLS_INSTALL)
diff --git a/source/lint/divergence_analysis.cpp b/source/lint/divergence_analysis.cpp
new file mode 100644
index 0000000..b5a72b4
--- /dev/null
+++ b/source/lint/divergence_analysis.cpp
@@ -0,0 +1,245 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/lint/divergence_analysis.h"
+
+#include "source/opt/basic_block.h"
+#include "source/opt/control_dependence.h"
+#include "source/opt/dataflow.h"
+#include "source/opt/function.h"
+#include "source/opt/instruction.h"
+#include "spirv/unified1/spirv.h"
+
+namespace spvtools {
+namespace lint {
+
+void DivergenceAnalysis::EnqueueSuccessors(opt::Instruction* inst) {
+  // Enqueue control dependents of block, if applicable.
+  // There are two ways for a dependence source to be updated:
+  // 1. control -> control: source block is marked divergent.
+  // 2. data -> control: branch condition is marked divergent.
+  uint32_t block_id;
+  if (inst->IsBlockTerminator()) {
+    block_id = context().get_instr_block(inst)->id();
+  } else if (inst->opcode() == SpvOpLabel) {
+    block_id = inst->result_id();
+    opt::BasicBlock* bb = context().cfg()->block(block_id);
+    // Only enqueue phi instructions, as other uses don't affect divergence.
+    bb->ForEachPhiInst([this](opt::Instruction* phi) { Enqueue(phi); });
+  } else {
+    opt::ForwardDataFlowAnalysis::EnqueueUsers(inst);
+    return;
+  }
+  if (!cd_.HasBlock(block_id)) {
+    return;
+  }
+  for (const spvtools::opt::ControlDependence& dep :
+       cd_.GetDependenceTargets(block_id)) {
+    opt::Instruction* target_inst =
+        context().cfg()->block(dep.target_bb_id())->GetLabelInst();
+    Enqueue(target_inst);
+  }
+}
+
+opt::DataFlowAnalysis::VisitResult DivergenceAnalysis::Visit(
+    opt::Instruction* inst) {
+  if (inst->opcode() == SpvOpLabel) {
+    return VisitBlock(inst->result_id());
+  } else {
+    return VisitInstruction(inst);
+  }
+}
+
+opt::DataFlowAnalysis::VisitResult DivergenceAnalysis::VisitBlock(uint32_t id) {
+  if (!cd_.HasBlock(id)) {
+    return opt::DataFlowAnalysis::VisitResult::kResultFixed;
+  }
+  DivergenceLevel& cur_level = divergence_[id];
+  if (cur_level == DivergenceLevel::kDivergent) {
+    return opt::DataFlowAnalysis::VisitResult::kResultFixed;
+  }
+  DivergenceLevel orig = cur_level;
+  for (const spvtools::opt::ControlDependence& dep :
+       cd_.GetDependenceSources(id)) {
+    if (divergence_[dep.source_bb_id()] > cur_level) {
+      cur_level = divergence_[dep.source_bb_id()];
+      divergence_source_[id] = dep.source_bb_id();
+    } else if (dep.source_bb_id() != 0) {
+      uint32_t condition_id = dep.GetConditionID(*context().cfg());
+      DivergenceLevel dep_level = divergence_[condition_id];
+      // Check if we are along the chain of unconditional branches starting from
+      // the branch target.
+      if (follow_unconditional_branches_[dep.branch_target_bb_id()] !=
+          follow_unconditional_branches_[dep.target_bb_id()]) {
+        // We must have reconverged in order to reach this block.
+        // Promote partially uniform to divergent.
+        if (dep_level == DivergenceLevel::kPartiallyUniform) {
+          dep_level = DivergenceLevel::kDivergent;
+        }
+      }
+      if (dep_level > cur_level) {
+        cur_level = dep_level;
+        divergence_source_[id] = condition_id;
+        divergence_dependence_source_[id] = dep.source_bb_id();
+      }
+    }
+  }
+  return cur_level > orig ? VisitResult::kResultChanged
+                          : VisitResult::kResultFixed;
+}
+
+opt::DataFlowAnalysis::VisitResult DivergenceAnalysis::VisitInstruction(
+    opt::Instruction* inst) {
+  if (inst->IsBlockTerminator()) {
+    // This is called only when the condition has changed, so return changed.
+    return VisitResult::kResultChanged;
+  }
+  if (!inst->HasResultId()) {
+    return VisitResult::kResultFixed;
+  }
+  uint32_t id = inst->result_id();
+  DivergenceLevel& cur_level = divergence_[id];
+  if (cur_level == DivergenceLevel::kDivergent) {
+    return opt::DataFlowAnalysis::VisitResult::kResultFixed;
+  }
+  DivergenceLevel orig = cur_level;
+  cur_level = ComputeInstructionDivergence(inst);
+  return cur_level > orig ? VisitResult::kResultChanged
+                          : VisitResult::kResultFixed;
+}
+
+DivergenceAnalysis::DivergenceLevel
+DivergenceAnalysis::ComputeInstructionDivergence(opt::Instruction* inst) {
+  // TODO(kuhar): Check to see if inst is decorated with Uniform or UniformId
+  // and use that to short circuit other checks. Uniform is for subgroups which
+  // would satisfy derivative groups too. UniformId takes a scope, so if it is
+  // subgroup or greater it could satisfy derivative group and
+  // Device/QueueFamily could satisfy fully uniform.
+  uint32_t id = inst->result_id();
+  // Handle divergence roots.
+  if (inst->opcode() == SpvOpFunctionParameter) {
+    divergence_source_[id] = 0;
+    return divergence_[id] = DivergenceLevel::kDivergent;
+  } else if (inst->IsLoad()) {
+    spvtools::opt::Instruction* var = inst->GetBaseAddress();
+    if (var->opcode() != SpvOpVariable) {
+      // Assume divergent.
+      divergence_source_[id] = 0;
+      return DivergenceLevel::kDivergent;
+    }
+    DivergenceLevel ret = ComputeVariableDivergence(var);
+    if (ret > DivergenceLevel::kUniform) {
+      divergence_source_[inst->result_id()] = 0;
+    }
+    return divergence_[id] = ret;
+  }
+  // Get the maximum divergence of the operands.
+  DivergenceLevel ret = DivergenceLevel::kUniform;
+  inst->ForEachInId([this, inst, &ret](const uint32_t* op) {
+    if (!op) return;
+    if (divergence_[*op] > ret) {
+      divergence_source_[inst->result_id()] = *op;
+      ret = divergence_[*op];
+    }
+  });
+  divergence_[inst->result_id()] = ret;
+  return ret;
+}
+
+DivergenceAnalysis::DivergenceLevel
+DivergenceAnalysis::ComputeVariableDivergence(opt::Instruction* var) {
+  uint32_t type_id = var->type_id();
+  spvtools::opt::analysis::Pointer* type =
+      context().get_type_mgr()->GetType(type_id)->AsPointer();
+  assert(type != nullptr);
+  uint32_t def_id = var->result_id();
+  DivergenceLevel ret;
+  switch (type->storage_class()) {
+    case SpvStorageClassFunction:
+    case SpvStorageClassGeneric:
+    case SpvStorageClassAtomicCounter:
+    case SpvStorageClassStorageBuffer:
+    case SpvStorageClassPhysicalStorageBuffer:
+    case SpvStorageClassOutput:
+    case SpvStorageClassWorkgroup:
+    case SpvStorageClassImage:  // Image atomics probably aren't uniform.
+    case SpvStorageClassPrivate:
+      ret = DivergenceLevel::kDivergent;
+      break;
+    case SpvStorageClassInput:
+      ret = DivergenceLevel::kDivergent;
+      // If this variable has a Flat decoration, it is partially uniform.
+      // TODO(kuhar): Track access chain indices and also consider Flat members
+      // of a structure.
+      context().get_decoration_mgr()->WhileEachDecoration(
+          def_id, SpvDecorationFlat, [&ret](const opt::Instruction&) {
+            ret = DivergenceLevel::kPartiallyUniform;
+            return false;
+          });
+      break;
+    case SpvStorageClassUniformConstant:
+      // May be a storage image which is also written to; mark those as
+      // divergent.
+      if (!var->IsVulkanStorageImage() || var->IsReadOnlyPointer()) {
+        ret = DivergenceLevel::kUniform;
+      } else {
+        ret = DivergenceLevel::kDivergent;
+      }
+      break;
+    case SpvStorageClassUniform:
+    case SpvStorageClassPushConstant:
+    case SpvStorageClassCrossWorkgroup:  // Not for shaders; default uniform.
+    default:
+      ret = DivergenceLevel::kUniform;
+      break;
+  }
+  return ret;
+}
+
+void DivergenceAnalysis::Setup(opt::Function* function) {
+  // TODO(kuhar): Run functions called by |function| so we can detect
+  // reconvergence caused by multiple returns.
+  cd_.ComputeControlDependenceGraph(
+      *context().cfg(), *context().GetPostDominatorAnalysis(function));
+  context().cfg()->ForEachBlockInPostOrder(
+      function->entry().get(), [this](const opt::BasicBlock* bb) {
+        uint32_t id = bb->id();
+        if (bb->terminator() == nullptr ||
+            bb->terminator()->opcode() != SpvOpBranch) {
+          follow_unconditional_branches_[id] = id;
+        } else {
+          uint32_t target_id = bb->terminator()->GetSingleWordInOperand(0);
+          // Target is guaranteed to have been visited before us in postorder.
+          follow_unconditional_branches_[id] =
+              follow_unconditional_branches_[target_id];
+        }
+      });
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         DivergenceAnalysis::DivergenceLevel level) {
+  switch (level) {
+    case DivergenceAnalysis::DivergenceLevel::kUniform:
+      return os << "uniform";
+    case DivergenceAnalysis::DivergenceLevel::kPartiallyUniform:
+      return os << "partially uniform";
+    case DivergenceAnalysis::DivergenceLevel::kDivergent:
+      return os << "divergent";
+    default:
+      return os << "<invalid divergence level>";
+  }
+}
+
+}  // namespace lint
+}  // namespace spvtools
diff --git a/source/lint/divergence_analysis.h b/source/lint/divergence_analysis.h
new file mode 100644
index 0000000..4d595ec
--- /dev/null
+++ b/source/lint/divergence_analysis.h
@@ -0,0 +1,163 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_LINT_DIVERGENCE_ANALYSIS_H_
+#define SOURCE_LINT_DIVERGENCE_ANALYSIS_H_
+
+#include <cstdint>
+#include <ostream>
+#include <unordered_map>
+
+#include "source/opt/basic_block.h"
+#include "source/opt/control_dependence.h"
+#include "source/opt/dataflow.h"
+#include "source/opt/function.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace lint {
+
+// Computes the static divergence level for blocks (control flow) and values.
+//
+// A value is uniform if all threads that execute it are guaranteed to have the
+// same value. Similarly, a value is partially uniform if this is true only
+// within each derivative group. If neither apply, it is divergent.
+//
+// Control flow through a block is uniform if for any possible execution and
+// point in time, all threads are executing it, or no threads are executing it.
+// In particular, it is never possible for some threads to be inside the block
+// and some threads not executing.
+// TODO(kuhar): Clarify the difference between uniform, divergent, and
+// partially-uniform execution in this analysis.
+//
+// Caveat:
+// As we use control dependence to determine how divergence is propagated, this
+// analysis can be overly permissive when the merge block for a conditional
+// branch or switch is later than (strictly postdominates) the expected merge
+// block, which is the immediate postdominator. However, this is not expected to
+// be a problem in practice, given that SPIR-V is generally output by compilers
+// and other automated tools, which would assign the earliest possible merge
+// block, rather than written by hand.
+// TODO(kuhar): Handle late merges.
+class DivergenceAnalysis : public opt::ForwardDataFlowAnalysis {
+ public:
+  // The tightest (most uniform) level of divergence that can be determined
+  // statically for a value or control flow for a block.
+  //
+  // The values are ordered such that A > B means that A is potentially more
+  // divergent than B.
+  // TODO(kuhar): Rename |PartiallyUniform' to something less confusing. For
+  // example, the enum could be based on scopes.
+  enum class DivergenceLevel {
+    // The value or control flow is uniform across the entire invocation group.
+    kUniform = 0,
+    // The value or control flow is uniform across the derivative group, but not
+    // the invocation group.
+    kPartiallyUniform = 1,
+    // The value or control flow is not statically uniform.
+    kDivergent = 2,
+  };
+
+  DivergenceAnalysis(opt::IRContext& context)
+      : ForwardDataFlowAnalysis(context, LabelPosition::kLabelsAtEnd) {}
+
+  // Returns the divergence level for the given value (non-label instructions),
+  // or control flow for the given block.
+  DivergenceLevel GetDivergenceLevel(uint32_t id) {
+    auto it = divergence_.find(id);
+    if (it == divergence_.end()) {
+      return DivergenceLevel::kUniform;
+    }
+    return it->second;
+  }
+
+  // Returns the divergence source for the given id. The following types of
+  // divergence flows from A to B are possible:
+  //
+  // data -> data: A is used as an operand in the definition of B.
+  // data -> control: B is control-dependent on a branch with condition A.
+  // control -> data: B is a OpPhi instruction in which A is a block operand.
+  // control -> control: B is control-dependent on A.
+  uint32_t GetDivergenceSource(uint32_t id) {
+    auto it = divergence_source_.find(id);
+    if (it == divergence_source_.end()) {
+      return 0;
+    }
+    return it->second;
+  }
+
+  // Returns the dependence source for the control dependence for the given id.
+  // This only exists for data -> control edges.
+  //
+  // In other words, if block 2 is dependent on block 1 due to value 3 (e.g.
+  // block 1 terminates with OpBranchConditional %3 %2 %4):
+  // * GetDivergenceSource(2) = 3
+  // * GetDivergenceDependenceSource(2) = 1
+  //
+  // Returns 0 if not applicable.
+  uint32_t GetDivergenceDependenceSource(uint32_t id) {
+    auto it = divergence_dependence_source_.find(id);
+    if (it == divergence_dependence_source_.end()) {
+      return 0;
+    }
+    return it->second;
+  }
+
+  void InitializeWorklist(opt::Function* function,
+                          bool is_first_iteration) override {
+    // Since |EnqueueSuccessors| is complete, we only need one pass.
+    if (is_first_iteration) {
+      Setup(function);
+      opt::ForwardDataFlowAnalysis::InitializeWorklist(function, true);
+    }
+  }
+
+  void EnqueueSuccessors(opt::Instruction* inst) override;
+
+  VisitResult Visit(opt::Instruction* inst) override;
+
+ private:
+  VisitResult VisitBlock(uint32_t id);
+  VisitResult VisitInstruction(opt::Instruction* inst);
+
+  // Computes the divergence level for the result of the given instruction
+  // based on the current state of the analysis. This is always an
+  // underapproximation, which will be improved as the analysis proceeds.
+  DivergenceLevel ComputeInstructionDivergence(opt::Instruction* inst);
+
+  // Computes the divergence level for a variable, which is used for loads.
+  DivergenceLevel ComputeVariableDivergence(opt::Instruction* var);
+
+  // Initializes data structures for performing dataflow on the given function.
+  void Setup(opt::Function* function);
+
+  std::unordered_map<uint32_t, DivergenceLevel> divergence_;
+  std::unordered_map<uint32_t, uint32_t> divergence_source_;
+  std::unordered_map<uint32_t, uint32_t> divergence_dependence_source_;
+
+  // Stores the result of following unconditional branches starting from the
+  // given block. This is used to detect when reconvergence needs to be
+  // accounted for.
+  std::unordered_map<uint32_t, uint32_t> follow_unconditional_branches_;
+
+  opt::ControlDependenceAnalysis cd_;
+};
+
+std::ostream& operator<<(std::ostream& os,
+                         DivergenceAnalysis::DivergenceLevel level);
+
+}  // namespace lint
+}  // namespace spvtools
+
+#endif  // SOURCE_LINT_DIVERGENCE_ANALYSIS_H_
diff --git a/source/lint/lint_divergent_derivatives.cpp b/source/lint/lint_divergent_derivatives.cpp
new file mode 100644
index 0000000..512847b
--- /dev/null
+++ b/source/lint/lint_divergent_derivatives.cpp
@@ -0,0 +1,169 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cassert>
+#include <sstream>
+#include <string>
+
+#include "source/diagnostic.h"
+#include "source/lint/divergence_analysis.h"
+#include "source/lint/lints.h"
+#include "source/opt/basic_block.h"
+#include "source/opt/cfg.h"
+#include "source/opt/control_dependence.h"
+#include "source/opt/def_use_manager.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.h"
+#include "spirv/unified1/spirv.h"
+
+namespace spvtools {
+namespace lint {
+namespace lints {
+namespace {
+// Returns the %name[id], where `name` is the first name associated with the
+// given id, or just %id if one is not found.
+std::string GetFriendlyName(opt::IRContext* context, uint32_t id) {
+  auto names = context->GetNames(id);
+  std::stringstream ss;
+  ss << "%";
+  if (names.empty()) {
+    ss << id;
+  } else {
+    opt::Instruction* inst_name = names.begin()->second;
+    if (inst_name->opcode() == SpvOpName) {
+      ss << names.begin()->second->GetInOperand(0).AsString();
+      ss << "[" << id << "]";
+    } else {
+      ss << id;
+    }
+  }
+  return ss.str();
+}
+
+bool InstructionHasDerivative(const opt::Instruction& inst) {
+  static const SpvOp derivative_opcodes[] = {
+      // Implicit derivatives.
+      SpvOpImageSampleImplicitLod,
+      SpvOpImageSampleDrefImplicitLod,
+      SpvOpImageSampleProjImplicitLod,
+      SpvOpImageSampleProjDrefImplicitLod,
+      SpvOpImageSparseSampleImplicitLod,
+      SpvOpImageSparseSampleDrefImplicitLod,
+      SpvOpImageSparseSampleProjImplicitLod,
+      SpvOpImageSparseSampleProjDrefImplicitLod,
+      // Explicit derivatives.
+      SpvOpDPdx,
+      SpvOpDPdy,
+      SpvOpFwidth,
+      SpvOpDPdxFine,
+      SpvOpDPdyFine,
+      SpvOpFwidthFine,
+      SpvOpDPdxCoarse,
+      SpvOpDPdyCoarse,
+      SpvOpFwidthCoarse,
+  };
+  return std::find(std::begin(derivative_opcodes), std::end(derivative_opcodes),
+                   inst.opcode()) != std::end(derivative_opcodes);
+}
+
+spvtools::DiagnosticStream Warn(opt::IRContext* context,
+                                opt::Instruction* inst) {
+  if (inst == nullptr) {
+    return DiagnosticStream({0, 0, 0}, context->consumer(), "", SPV_WARNING);
+  } else {
+    // TODO(kuhar): Use line numbers based on debug info.
+    return DiagnosticStream(
+        {0, 0, 0}, context->consumer(),
+        inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES),
+        SPV_WARNING);
+  }
+}
+
+void PrintDivergenceFlow(opt::IRContext* context, DivergenceAnalysis div,
+                         uint32_t id) {
+  opt::analysis::DefUseManager* def_use = context->get_def_use_mgr();
+  opt::CFG* cfg = context->cfg();
+  while (id != 0) {
+    bool is_block = def_use->GetDef(id)->opcode() == SpvOpLabel;
+    if (is_block) {
+      Warn(context, nullptr)
+          << "block " << GetFriendlyName(context, id) << " is divergent";
+      uint32_t source = div.GetDivergenceSource(id);
+      // Skip intermediate blocks.
+      while (source != 0 && def_use->GetDef(source)->opcode() == SpvOpLabel) {
+        id = source;
+        source = div.GetDivergenceSource(id);
+      }
+      if (source == 0) break;
+      spvtools::opt::Instruction* branch =
+          cfg->block(div.GetDivergenceDependenceSource(id))->terminator();
+      Warn(context, branch)
+          << "because it depends on a conditional branch on divergent value "
+          << GetFriendlyName(context, source) << "";
+      id = source;
+    } else {
+      Warn(context, nullptr)
+          << "value " << GetFriendlyName(context, id) << " is divergent";
+      uint32_t source = div.GetDivergenceSource(id);
+      opt::Instruction* def = def_use->GetDef(id);
+      opt::Instruction* source_def =
+          source == 0 ? nullptr : def_use->GetDef(source);
+      // First print data -> data dependencies.
+      while (source != 0 && source_def->opcode() != SpvOpLabel) {
+        Warn(context, def_use->GetDef(id))
+            << "because " << GetFriendlyName(context, id) << " uses value "
+            << GetFriendlyName(context, source)
+            << "in its definition, which is divergent";
+        id = source;
+        def = source_def;
+        source = div.GetDivergenceSource(id);
+        source_def = def_use->GetDef(source);
+      }
+      if (source == 0) {
+        Warn(context, def) << "because it has a divergent definition";
+        break;
+      }
+      Warn(context, def) << "because it is conditionally set in block "
+                         << GetFriendlyName(context, source);
+      id = source;
+    }
+  }
+}
+}  // namespace
+
+bool CheckDivergentDerivatives(opt::IRContext* context) {
+  DivergenceAnalysis div(*context);
+  for (opt::Function& func : *context->module()) {
+    div.Run(&func);
+    for (const opt::BasicBlock& bb : func) {
+      for (const opt::Instruction& inst : bb) {
+        if (InstructionHasDerivative(inst) &&
+            div.GetDivergenceLevel(bb.id()) >
+                DivergenceAnalysis::DivergenceLevel::kPartiallyUniform) {
+          Warn(context, nullptr)
+              << "derivative with divergent control flow"
+              << " located in block " << GetFriendlyName(context, bb.id());
+          PrintDivergenceFlow(context, div, bb.id());
+        }
+      }
+    }
+  }
+  return true;
+}
+
+}  // namespace lints
+}  // namespace lint
+}  // namespace spvtools
diff --git a/source/lint/linter.cpp b/source/lint/linter.cpp
new file mode 100644
index 0000000..e4ed04e
--- /dev/null
+++ b/source/lint/linter.cpp
@@ -0,0 +1,60 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "spirv-tools/linter.hpp"
+
+#include "source/lint/lints.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.h"
+#include "spirv-tools/libspirv.hpp"
+#include "spirv/unified1/spirv.h"
+
+namespace spvtools {
+
+struct Linter::Impl {
+  explicit Impl(spv_target_env env) : target_env(env) {
+    message_consumer = [](spv_message_level_t /*level*/, const char* /*source*/,
+                          const spv_position_t& /*position*/,
+                          const char* /*message*/) {};
+  }
+
+  spv_target_env target_env;         // Target environment.
+  MessageConsumer message_consumer;  // Message consumer.
+};
+
+Linter::Linter(spv_target_env env) : impl_(new Impl(env)) {}
+
+Linter::~Linter() {}
+
+void Linter::SetMessageConsumer(MessageConsumer consumer) {
+  impl_->message_consumer = std::move(consumer);
+}
+
+const MessageConsumer& Linter::Consumer() const {
+  return impl_->message_consumer;
+}
+
+bool Linter::Run(const uint32_t* binary, size_t binary_size) {
+  std::unique_ptr<opt::IRContext> context =
+      BuildModule(SPV_ENV_VULKAN_1_2, Consumer(), binary, binary_size);
+  if (context == nullptr) return false;
+
+  bool result = true;
+  result &= lint::lints::CheckDivergentDerivatives(context.get());
+
+  return result;
+}
+
+}  // namespace spvtools
diff --git a/source/lint/lints.h b/source/lint/lints.h
new file mode 100644
index 0000000..a1995d2
--- /dev/null
+++ b/source/lint/lints.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_LINT_LINTS_H_
+#define SOURCE_LINT_LINTS_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace lint {
+
+// All of the functions in this namespace output to the error consumer in the
+// |context| argument and return |true| if no errors are found. They do not
+// modify the IR.
+namespace lints {
+
+bool CheckDivergentDerivatives(opt::IRContext* context);
+
+}  // namespace lints
+}  // namespace lint
+}  // namespace spvtools
+
+#endif  // SOURCE_LINT_LINTS_H_
diff --git a/source/opcode.cpp b/source/opcode.cpp
index d87e828..c96cde8 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -417,8 +417,10 @@
     case SpvOpAtomicISub:
     case SpvOpAtomicSMin:
     case SpvOpAtomicUMin:
+    case SpvOpAtomicFMinEXT:
     case SpvOpAtomicSMax:
     case SpvOpAtomicUMax:
+    case SpvOpAtomicFMaxEXT:
     case SpvOpAtomicAnd:
     case SpvOpAtomicOr:
     case SpvOpAtomicXor:
diff --git a/source/operand.cpp b/source/operand.cpp
index 5a69fb2..6d83e81 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -229,6 +229,9 @@
       return "ray query committed intersection type";
     case SPV_OPERAND_TYPE_RAY_QUERY_CANDIDATE_INTERSECTION_TYPE:
       return "ray query candidate intersection type";
+    case SPV_OPERAND_TYPE_PACKED_VECTOR_FORMAT:
+    case SPV_OPERAND_TYPE_OPTIONAL_PACKED_VECTOR_FORMAT:
+      return "packed vector format";
     case SPV_OPERAND_TYPE_IMAGE:
     case SPV_OPERAND_TYPE_OPTIONAL_IMAGE:
       return "image";
@@ -269,6 +272,10 @@
       return "FP denorm mode";
     case SPV_OPERAND_TYPE_FPOPERATION_MODE:
       return "FP operation mode";
+    case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
+      return "quantization mode";
+    case SPV_OPERAND_TYPE_OVERFLOW_MODES:
+      return "overflow mode";
 
     case SPV_OPERAND_TYPE_NONE:
       return "NONE";
@@ -355,6 +362,9 @@
     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_IMPORTED_ENTITY:
     case SPV_OPERAND_TYPE_FPDENORM_MODE:
     case SPV_OPERAND_TYPE_FPOPERATION_MODE:
+    case SPV_OPERAND_TYPE_QUANTIZATION_MODES:
+    case SPV_OPERAND_TYPE_OVERFLOW_MODES:
+    case SPV_OPERAND_TYPE_PACKED_VECTOR_FORMAT:
       return true;
     default:
       break;
@@ -390,6 +400,7 @@
     case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER:
     case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING:
     case SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER:
+    case SPV_OPERAND_TYPE_OPTIONAL_PACKED_VECTOR_FORMAT:
     case SPV_OPERAND_TYPE_OPTIONAL_CIV:
       return true;
     default:
@@ -567,6 +578,12 @@
 
 std::function<bool(unsigned)> spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
     spv_ext_inst_type_t ext_type, uint32_t key) {
+  // The Vulkan debug info extended instruction set is non-semantic so allows no
+  // forward references ever
+  if (ext_type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
+    return [](unsigned) { return false; };
+  }
+
   // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/532): Forward
   // references for debug info instructions are still in discussion. We must
   // update the following lines of code when we conclude the spec.
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 14a6bee..7d522fb 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -27,8 +27,11 @@
   composite.h
   const_folding_rules.h
   constants.h
+  control_dependence.h
+  convert_to_sampled_image_pass.h
   convert_to_half_pass.h
   copy_prop_arrays.h
+  dataflow.h
   dead_branch_elim_pass.h
   dead_insert_elim_pass.h
   dead_variable_elimination.h
@@ -36,6 +39,7 @@
   debug_info_manager.h
   def_use_manager.h
   desc_sroa.h
+  desc_sroa_util.h
   dominator_analysis.h
   dominator_tree.h
   eliminate_dead_constant_pass.h
@@ -62,6 +66,7 @@
   instruction.h
   instruction_list.h
   instrument_pass.h
+  interp_fixup_pass.h
   ir_builder.h
   ir_context.h
   ir_loader.h
@@ -95,6 +100,8 @@
   register_pressure.h
   relax_float_ops_pass.h
   remove_duplicates_pass.h
+  remove_unused_interface_variables_pass.h
+  replace_desc_array_access_using_var_index.h
   replace_invalid_opc.h
   scalar_analysis.h
   scalar_analysis_nodes.h
@@ -131,8 +138,11 @@
   composite.cpp
   const_folding_rules.cpp
   constants.cpp
+  control_dependence.cpp
+  convert_to_sampled_image_pass.cpp
   convert_to_half_pass.cpp
   copy_prop_arrays.cpp
+  dataflow.cpp
   dead_branch_elim_pass.cpp
   dead_insert_elim_pass.cpp
   dead_variable_elimination.cpp
@@ -140,6 +150,7 @@
   debug_info_manager.cpp
   def_use_manager.cpp
   desc_sroa.cpp
+  desc_sroa_util.cpp
   dominator_analysis.cpp
   dominator_tree.cpp
   eliminate_dead_constant_pass.cpp
@@ -165,6 +176,7 @@
   instruction.cpp
   instruction_list.cpp
   instrument_pass.cpp
+  interp_fixup_pass.cpp
   ir_context.cpp
   ir_loader.cpp
   licm_pass.cpp
@@ -195,6 +207,8 @@
   register_pressure.cpp
   relax_float_ops_pass.cpp
   remove_duplicates_pass.cpp
+  remove_unused_interface_variables_pass.cpp
+  replace_desc_array_access_using_var_index.cpp
   replace_invalid_opc.cpp
   scalar_analysis.cpp
   scalar_analysis_simplification.cpp
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index 81b2232..0b54d5e 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -1,7 +1,7 @@
 // Copyright (c) 2017 The Khronos Group Inc.
 // Copyright (c) 2017 Valve Corporation
 // Copyright (c) 2017 LunarG Inc.
-// Copyright (c) 2018 Google LLC
+// Copyright (c) 2018-2021 Google LLC
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
 #include "source/cfa.h"
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/opt/eliminate_dead_functions_util.h"
+#include "source/opt/ir_builder.h"
 #include "source/opt/iterator.h"
 #include "source/opt/reflect.h"
 #include "source/spirv_constant.h"
@@ -35,10 +36,10 @@
 const uint32_t kTypePointerStorageClassInIdx = 0;
 const uint32_t kEntryPointFunctionIdInIdx = 1;
 const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
-const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
 const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
 const uint32_t kCopyMemoryTargetAddrInIdx = 0;
 const uint32_t kCopyMemorySourceAddrInIdx = 1;
+const uint32_t kLoadSourceAddrInIdx = 0;
 const uint32_t kDebugDeclareOperandVariableIndex = 5;
 const uint32_t kGlobalVariableVariableIndex = 12;
 
@@ -96,16 +97,21 @@
          storageClass;
 }
 
-bool AggressiveDCEPass::IsLocalVar(uint32_t varId) {
+bool AggressiveDCEPass::IsLocalVar(uint32_t varId, Function* func) {
   if (IsVarOfStorage(varId, SpvStorageClassFunction)) {
     return true;
   }
-  if (!private_like_local_) {
+
+  if (!IsVarOfStorage(varId, SpvStorageClassPrivate) &&
+      !IsVarOfStorage(varId, SpvStorageClassWorkgroup)) {
     return false;
   }
 
-  return IsVarOfStorage(varId, SpvStorageClassPrivate) ||
-         IsVarOfStorage(varId, SpvStorageClassWorkgroup);
+  // For a variable in the Private or WorkGroup storage class, the variable will
+  // get a new instance for every call to an entry point.  If the entry point
+  // does not have a call, then no other function can read or write to that
+  // instance of the variable.
+  return IsEntryPointWithNoCalls(func);
 }
 
 void AggressiveDCEPass::AddStores(Function* func, uint32_t ptrId) {
@@ -145,15 +151,19 @@
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
-  return true;
-}
-
-bool AggressiveDCEPass::IsDead(Instruction* inst) {
-  if (IsLive(inst)) return false;
-  if ((inst->IsBranch() || inst->opcode() == SpvOpUnreachable) &&
-      !IsStructuredHeader(context()->get_instr_block(inst), nullptr, nullptr,
-                          nullptr))
-    return false;
+  // Only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise
+  // around unknown extended instruction sets even if they are non-semantic
+  for (auto& inst : context()->module()->ext_inst_imports()) {
+    assert(inst.opcode() == SpvOpExtInstImport &&
+           "Expecting an import of an extension's instruction set.");
+    const char* extension_name =
+        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
+    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
+        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
+                          32)) {
+      return false;
+    }
+  }
   return true;
 }
 
@@ -173,12 +183,12 @@
     });
     return dead;
   }
-  return IsDead(tInst);
+  return !IsLive(tInst);
 }
 
 void AggressiveDCEPass::ProcessLoad(Function* func, uint32_t varId) {
   // Only process locals
-  if (!IsLocalVar(varId)) return;
+  if (!IsLocalVar(varId, func)) return;
   // Return if already processed
   if (live_local_vars_.find(varId) != live_local_vars_.end()) return;
   // Mark all stores to varId as live
@@ -187,66 +197,6 @@
   live_local_vars_.insert(varId);
 }
 
-bool AggressiveDCEPass::IsStructuredHeader(BasicBlock* bp,
-                                           Instruction** mergeInst,
-                                           Instruction** branchInst,
-                                           uint32_t* mergeBlockId) {
-  if (!bp) return false;
-  Instruction* mi = bp->GetMergeInst();
-  if (mi == nullptr) return false;
-  Instruction* bri = &*bp->tail();
-  if (branchInst != nullptr) *branchInst = bri;
-  if (mergeInst != nullptr) *mergeInst = mi;
-  if (mergeBlockId != nullptr) *mergeBlockId = mi->GetSingleWordInOperand(0);
-  return true;
-}
-
-void AggressiveDCEPass::ComputeBlock2HeaderMaps(
-    std::list<BasicBlock*>& structuredOrder) {
-  block2headerBranch_.clear();
-  header2nextHeaderBranch_.clear();
-  branch2merge_.clear();
-  structured_order_index_.clear();
-  std::stack<Instruction*> currentHeaderBranch;
-  currentHeaderBranch.push(nullptr);
-  uint32_t currentMergeBlockId = 0;
-  uint32_t index = 0;
-  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();
-       ++bi, ++index) {
-    structured_order_index_[*bi] = index;
-    // If this block is the merge block of the current control construct,
-    // we are leaving the current construct so we must update state
-    if ((*bi)->id() == currentMergeBlockId) {
-      currentHeaderBranch.pop();
-      Instruction* chb = currentHeaderBranch.top();
-      if (chb != nullptr)
-        currentMergeBlockId = branch2merge_[chb]->GetSingleWordInOperand(0);
-    }
-    Instruction* mergeInst;
-    Instruction* branchInst;
-    uint32_t mergeBlockId;
-    bool is_header =
-        IsStructuredHeader(*bi, &mergeInst, &branchInst, &mergeBlockId);
-    // Map header block to next enclosing header.
-    if (is_header) header2nextHeaderBranch_[*bi] = currentHeaderBranch.top();
-    // If this is a loop header, update state first so the block will map to
-    // itself.
-    if (is_header && mergeInst->opcode() == SpvOpLoopMerge) {
-      currentHeaderBranch.push(branchInst);
-      branch2merge_[branchInst] = mergeInst;
-      currentMergeBlockId = mergeBlockId;
-    }
-    // Map the block to the current construct.
-    block2headerBranch_[*bi] = currentHeaderBranch.top();
-    // If this is an if header, update state so following blocks map to the if.
-    if (is_header && mergeInst->opcode() == SpvOpSelectionMerge) {
-      currentHeaderBranch.push(branchInst);
-      branch2merge_[branchInst] = mergeInst;
-      currentMergeBlockId = mergeBlockId;
-    }
-  }
-}
-
 void AggressiveDCEPass::AddBranch(uint32_t labelId, BasicBlock* bp) {
   std::unique_ptr<Instruction> newBranch(
       new Instruction(context(), SpvOpBranch, 0, 0,
@@ -262,23 +212,18 @@
          mergeInst->opcode() == SpvOpLoopMerge);
 
   BasicBlock* header = context()->get_instr_block(mergeInst);
-  uint32_t headerIndex = structured_order_index_[header];
   const uint32_t mergeId = mergeInst->GetSingleWordInOperand(0);
-  BasicBlock* merge = context()->get_instr_block(mergeId);
-  uint32_t mergeIndex = structured_order_index_[merge];
-  get_def_use_mgr()->ForEachUser(
-      mergeId, [headerIndex, mergeIndex, this](Instruction* user) {
-        if (!user->IsBranch()) return;
-        BasicBlock* block = context()->get_instr_block(user);
-        uint32_t index = structured_order_index_[block];
-        if (headerIndex < index && index < mergeIndex) {
-          // This is a break from the loop.
-          AddToWorklist(user);
-          // Add branch's merge if there is one.
-          Instruction* userMerge = branch2merge_[user];
-          if (userMerge != nullptr) AddToWorklist(userMerge);
-        }
-      });
+  get_def_use_mgr()->ForEachUser(mergeId, [header, this](Instruction* user) {
+    if (!user->IsBranch()) return;
+    BasicBlock* block = context()->get_instr_block(user);
+    if (BlockIsInConstruct(header, block)) {
+      // This is a break from the loop.
+      AddToWorklist(user);
+      // Add branch's merge if there is one.
+      Instruction* userMerge = GetMergeInstruction(user);
+      if (userMerge != nullptr) AddToWorklist(userMerge);
+    }
+  });
 
   if (mergeInst->opcode() != SpvOpLoopMerge) {
     return;
@@ -292,7 +237,7 @@
     if (op == SpvOpBranchConditional || op == SpvOpSwitch) {
       // A conditional branch or switch can only be a continue if it does not
       // have a merge instruction or its merge block is not the continue block.
-      Instruction* hdrMerge = branch2merge_[user];
+      Instruction* hdrMerge = GetMergeInstruction(user);
       if (hdrMerge != nullptr && hdrMerge->opcode() == SpvOpSelectionMerge) {
         uint32_t hdrMergeId =
             hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
@@ -304,9 +249,9 @@
       // An unconditional branch can only be a continue if it is not
       // branching to its own merge block.
       BasicBlock* blk = context()->get_instr_block(user);
-      Instruction* hdrBranch = block2headerBranch_[blk];
+      Instruction* hdrBranch = GetHeaderBranch(blk);
       if (hdrBranch == nullptr) return;
-      Instruction* hdrMerge = branch2merge_[hdrBranch];
+      Instruction* hdrMerge = GetMergeInstruction(hdrBranch);
       if (hdrMerge->opcode() == SpvOpLoopMerge) return;
       uint32_t hdrMergeId =
           hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx);
@@ -319,254 +264,36 @@
 }
 
 bool AggressiveDCEPass::AggressiveDCE(Function* func) {
-  // Mark function parameters as live.
-  AddToWorklist(&func->DefInst());
-  func->ForEachParam(
-      [this](const Instruction* param) {
-        AddToWorklist(const_cast<Instruction*>(param));
-      },
-      false);
-
-  // Compute map from block to controlling conditional branch
-  std::list<BasicBlock*> structuredOrder;
-  cfg()->ComputeStructuredOrder(func, &*func->begin(), &structuredOrder);
-  ComputeBlock2HeaderMaps(structuredOrder);
-  bool modified = false;
-  // Add instructions with external side effects to worklist. Also add branches
-  // EXCEPT those immediately contained in an "if" selection construct or a loop
-  // or continue construct.
-  // TODO(greg-lunarg): Handle Frexp, Modf more optimally
-  call_in_func_ = false;
-  func_is_entry_point_ = false;
-  private_stores_.clear();
+  std::list<BasicBlock*> structured_order;
+  cfg()->ComputeStructuredOrder(func, &*func->begin(), &structured_order);
   live_local_vars_.clear();
-  // Stacks to keep track of when we are inside an if- or loop-construct.
-  // When immediately inside an if- or loop-construct, we do not initially
-  // mark branches live. All other branches must be marked live.
-  std::stack<bool> assume_branches_live;
-  std::stack<uint32_t> currentMergeBlockId;
-  // Push sentinel values on stack for when outside of any control flow.
-  assume_branches_live.push(true);
-  currentMergeBlockId.push(0);
-  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
-    // If exiting if or loop, update stacks
-    if ((*bi)->id() == currentMergeBlockId.top()) {
-      assume_branches_live.pop();
-      currentMergeBlockId.pop();
-    }
-    for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) {
-      SpvOp op = ii->opcode();
-      switch (op) {
-        case SpvOpStore: {
-          uint32_t varId;
-          (void)GetPtr(&*ii, &varId);
-          // Mark stores as live if their variable is not function scope
-          // and is not private scope. Remember private stores for possible
-          // later inclusion.  We cannot call IsLocalVar at this point because
-          // private_like_local_ has not been set yet.
-          if (IsVarOfStorage(varId, SpvStorageClassPrivate) ||
-              IsVarOfStorage(varId, SpvStorageClassWorkgroup))
-            private_stores_.push_back(&*ii);
-          else if (!IsVarOfStorage(varId, SpvStorageClassFunction))
-            AddToWorklist(&*ii);
-        } break;
-        case SpvOpCopyMemory:
-        case SpvOpCopyMemorySized: {
-          uint32_t varId;
-          (void)GetPtr(ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx),
-                       &varId);
-          if (IsVarOfStorage(varId, SpvStorageClassPrivate) ||
-              IsVarOfStorage(varId, SpvStorageClassWorkgroup))
-            private_stores_.push_back(&*ii);
-          else if (!IsVarOfStorage(varId, SpvStorageClassFunction))
-            AddToWorklist(&*ii);
-        } break;
-        case SpvOpLoopMerge: {
-          assume_branches_live.push(false);
-          currentMergeBlockId.push(
-              ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx));
-        } break;
-        case SpvOpSelectionMerge: {
-          assume_branches_live.push(false);
-          currentMergeBlockId.push(
-              ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx));
-        } break;
-        case SpvOpSwitch:
-        case SpvOpBranch:
-        case SpvOpBranchConditional:
-        case SpvOpUnreachable: {
-          if (assume_branches_live.top()) {
-            AddToWorklist(&*ii);
-          }
-        } break;
-        default: {
-          // Function calls, atomics, function params, function returns, etc.
-          // TODO(greg-lunarg): function calls live only if write to non-local
-          if (!ii->IsOpcodeSafeToDelete()) {
-            AddToWorklist(&*ii);
-          }
-          // Remember function calls
-          if (op == SpvOpFunctionCall) call_in_func_ = true;
-        } break;
-      }
-    }
-  }
-  // See if current function is an entry point
-  for (auto& ei : get_module()->entry_points()) {
-    if (ei.GetSingleWordInOperand(kEntryPointFunctionIdInIdx) ==
-        func->result_id()) {
-      func_is_entry_point_ = true;
-      break;
-    }
-  }
-  // If the current function is an entry point and has no function calls,
-  // we can optimize private variables as locals
-  private_like_local_ = func_is_entry_point_ && !call_in_func_;
-  // If privates are not like local, add their stores to worklist
-  if (!private_like_local_)
-    for (auto& ps : private_stores_) AddToWorklist(ps);
-  // Perform closure on live instruction set.
-  while (!worklist_.empty()) {
-    Instruction* liveInst = worklist_.front();
-    // Add all operand instructions if not already live
-    liveInst->ForEachInId([&liveInst, this](const uint32_t* iid) {
-      Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
-      // Do not add label if an operand of a branch. This is not needed
-      // as part of live code discovery and can create false live code,
-      // for example, the branch to a header of a loop.
-      if (inInst->opcode() == SpvOpLabel && liveInst->IsBranch()) return;
-      AddToWorklist(inInst);
-    });
-    if (liveInst->type_id() != 0) {
-      AddToWorklist(get_def_use_mgr()->GetDef(liveInst->type_id()));
-    }
-    // If in a structured if or loop construct, add the controlling
-    // conditional branch and its merge.
-    BasicBlock* blk = context()->get_instr_block(liveInst);
-    Instruction* branchInst = block2headerBranch_[blk];
-    if (branchInst != nullptr) {
-      AddToWorklist(branchInst);
-      Instruction* mergeInst = branch2merge_[branchInst];
-      AddToWorklist(mergeInst);
-    }
-    // If the block is a header, add the next outermost controlling
-    // conditional branch and its merge.
-    Instruction* nextBranchInst = header2nextHeaderBranch_[blk];
-    if (nextBranchInst != nullptr) {
-      AddToWorklist(nextBranchInst);
-      Instruction* mergeInst = branch2merge_[nextBranchInst];
-      AddToWorklist(mergeInst);
-    }
-    // If local load, add all variable's stores if variable not already live
-    if (liveInst->opcode() == SpvOpLoad || liveInst->IsAtomicWithLoad()) {
-      uint32_t varId;
-      (void)GetPtr(liveInst, &varId);
-      if (varId != 0) {
-        ProcessLoad(func, varId);
-      }
-      // Process memory copies like loads
-    } else if (liveInst->opcode() == SpvOpCopyMemory ||
-               liveInst->opcode() == SpvOpCopyMemorySized) {
-      uint32_t varId;
-      (void)GetPtr(liveInst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx),
-                   &varId);
-      if (varId != 0) {
-        ProcessLoad(func, varId);
-      }
-      // If DebugDeclare, process as load of variable
-    } else if (liveInst->GetOpenCL100DebugOpcode() ==
-               OpenCLDebugInfo100DebugDeclare) {
-      uint32_t varId =
-          liveInst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
-      ProcessLoad(func, varId);
-      // If DebugValue with Deref, process as load of variable
-    } else if (liveInst->GetOpenCL100DebugOpcode() ==
-               OpenCLDebugInfo100DebugValue) {
-      uint32_t varId = context()
-                           ->get_debug_info_mgr()
-                           ->GetVariableIdOfDebugValueUsedForDeclare(liveInst);
-      if (varId != 0) ProcessLoad(func, varId);
-      // If merge, add other branches that are part of its control structure
-    } else if (liveInst->opcode() == SpvOpLoopMerge ||
-               liveInst->opcode() == SpvOpSelectionMerge) {
-      AddBreaksAndContinuesToWorklist(liveInst);
-      // If function call, treat as if it loads from all pointer arguments
-    } else if (liveInst->opcode() == SpvOpFunctionCall) {
-      liveInst->ForEachInId([this, func](const uint32_t* iid) {
-        // Skip non-ptr args
-        if (!IsPtr(*iid)) return;
-        uint32_t varId;
-        (void)GetPtr(*iid, &varId);
-        ProcessLoad(func, varId);
-      });
-      // If function parameter, treat as if it's result id is loaded from
-    } else if (liveInst->opcode() == SpvOpFunctionParameter) {
-      ProcessLoad(func, liveInst->result_id());
-      // We treat an OpImageTexelPointer as a load of the pointer, and
-      // that value is manipulated to get the result.
-    } else if (liveInst->opcode() == SpvOpImageTexelPointer) {
-      uint32_t varId;
-      (void)GetPtr(liveInst, &varId);
-      if (varId != 0) {
-        ProcessLoad(func, varId);
-      }
-    }
+  InitializeWorkList(func, structured_order);
+  ProcessWorkList(func);
+  return KillDeadInstructions(func, structured_order);
+}
 
-    // 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);
-    }
-
-    // Add DebugScope and DebugInlinedAt for |liveInst| to the work list.
-    if (liveInst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
-      auto* scope = get_def_use_mgr()->GetDef(
-          liveInst->GetDebugScope().GetLexicalScope());
-      AddToWorklist(scope);
-    }
-    if (liveInst->GetDebugInlinedAt() != kNoInlinedAt) {
-      auto* inlined_at =
-          get_def_use_mgr()->GetDef(liveInst->GetDebugInlinedAt());
-      AddToWorklist(inlined_at);
-    }
-    worklist_.pop();
-  }
-
-  // Kill dead instructions and remember dead blocks
-  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) {
-    uint32_t mergeBlockId = 0;
-    (*bi)->ForEachInst([this, &modified, &mergeBlockId](Instruction* inst) {
-      if (!IsDead(inst)) return;
+bool AggressiveDCEPass::KillDeadInstructions(
+    const Function* func, std::list<BasicBlock*>& structured_order) {
+  bool modified = false;
+  for (auto bi = structured_order.begin(); bi != structured_order.end();) {
+    uint32_t merge_block_id = 0;
+    (*bi)->ForEachInst([this, &modified, &merge_block_id](Instruction* inst) {
+      if (IsLive(inst)) return;
       if (inst->opcode() == SpvOpLabel) return;
       // If dead instruction is selection merge, remember merge block
       // for new branch at end of block
       if (inst->opcode() == SpvOpSelectionMerge ||
           inst->opcode() == SpvOpLoopMerge)
-        mergeBlockId = inst->GetSingleWordInOperand(0);
+        merge_block_id = inst->GetSingleWordInOperand(0);
       to_kill_.push_back(inst);
       modified = true;
     });
     // If a structured if or loop was deleted, add a branch to its merge
     // block, and traverse to the merge block and continue processing there.
     // We know the block still exists because the label is not deleted.
-    if (mergeBlockId != 0) {
-      AddBranch(mergeBlockId, *bi);
-      for (++bi; (*bi)->id() != mergeBlockId; ++bi) {
+    if (merge_block_id != 0) {
+      AddBranch(merge_block_id, *bi);
+      for (++bi; (*bi)->id() != merge_block_id; ++bi) {
       }
 
       auto merge_terminator = (*bi)->terminator();
@@ -589,13 +316,252 @@
         live_insts_.Set(merge_terminator->unique_id());
       }
     } else {
+      Instruction* inst = (*bi)->terminator();
+      if (!IsLive(inst)) {
+        // If the terminator is not live, this block has no live instructions,
+        // and it will be unreachable.
+        AddUnreachable(*bi);
+      }
       ++bi;
     }
   }
-
   return modified;
 }
 
+void AggressiveDCEPass::ProcessWorkList(Function* func) {
+  while (!worklist_.empty()) {
+    Instruction* live_inst = worklist_.front();
+    worklist_.pop();
+    AddOperandsToWorkList(live_inst);
+    MarkBlockAsLive(live_inst);
+    MarkLoadedVariablesAsLive(func, live_inst);
+    AddDecorationsToWorkList(live_inst);
+    AddDebugInstructionsToWorkList(live_inst);
+  }
+}
+
+void AggressiveDCEPass::AddDebugInstructionsToWorkList(
+    const Instruction* inst) {
+  for (auto& line_inst : inst->dbg_line_insts()) {
+    if (line_inst.IsDebugLineInst()) {
+      AddOperandsToWorkList(&line_inst);
+    }
+  }
+
+  if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
+    auto* scope =
+        get_def_use_mgr()->GetDef(inst->GetDebugScope().GetLexicalScope());
+    AddToWorklist(scope);
+  }
+  if (inst->GetDebugInlinedAt() != kNoInlinedAt) {
+    auto* inlined_at = get_def_use_mgr()->GetDef(inst->GetDebugInlinedAt());
+    AddToWorklist(inlined_at);
+  }
+}
+
+void AggressiveDCEPass::AddDecorationsToWorkList(const Instruction* inst) {
+  // 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(inst->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);
+  }
+}
+
+void AggressiveDCEPass::MarkLoadedVariablesAsLive(Function* func,
+                                                  Instruction* inst) {
+  std::vector<uint32_t> live_variables = GetLoadedVariables(inst);
+  for (uint32_t var_id : live_variables) {
+    ProcessLoad(func, var_id);
+  }
+}
+
+std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariables(Instruction* inst) {
+  if (inst->opcode() == SpvOpFunctionCall) {
+    return GetLoadedVariablesFromFunctionCall(inst);
+  }
+  uint32_t var_id = GetLoadedVariableFromNonFunctionCalls(inst);
+  if (var_id == 0) {
+    return {};
+  }
+  return {var_id};
+}
+
+uint32_t AggressiveDCEPass::GetLoadedVariableFromNonFunctionCalls(
+    Instruction* inst) {
+  std::vector<uint32_t> live_variables;
+  if (inst->IsAtomicWithLoad()) {
+    return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx));
+  }
+
+  switch (inst->opcode()) {
+    case SpvOpLoad:
+    case SpvOpImageTexelPointer:
+      return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx));
+    case SpvOpCopyMemory:
+    case SpvOpCopyMemorySized:
+      return GetVariableId(
+          inst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx));
+    default:
+      break;
+  }
+
+  switch (inst->GetCommonDebugOpcode()) {
+    case CommonDebugInfoDebugDeclare:
+      return inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
+    case CommonDebugInfoDebugValue: {
+      analysis::DebugInfoManager* debug_info_mgr =
+          context()->get_debug_info_mgr();
+      return debug_info_mgr->GetVariableIdOfDebugValueUsedForDeclare(inst);
+    }
+    default:
+      break;
+  }
+  return 0;
+}
+
+std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariablesFromFunctionCall(
+    const Instruction* inst) {
+  assert(inst->opcode() == SpvOpFunctionCall);
+  std::vector<uint32_t> live_variables;
+  inst->ForEachInId([this, &live_variables](const uint32_t* operand_id) {
+    if (!IsPtr(*operand_id)) return;
+    uint32_t var_id = GetVariableId(*operand_id);
+    live_variables.push_back(var_id);
+  });
+  return live_variables;
+}
+
+uint32_t AggressiveDCEPass::GetVariableId(uint32_t ptr_id) {
+  assert(IsPtr(ptr_id) &&
+         "Cannot get the variable when input is not a pointer.");
+  uint32_t varId = 0;
+  (void)GetPtr(ptr_id, &varId);
+  return varId;
+}
+
+void AggressiveDCEPass::MarkBlockAsLive(Instruction* inst) {
+  BasicBlock* basic_block = context()->get_instr_block(inst);
+  if (basic_block == nullptr) {
+    return;
+  }
+
+  // If we intend to keep this instruction, we need the block label and
+  // block terminator to have a valid block for the instruction.
+  AddToWorklist(basic_block->GetLabelInst());
+
+  // We need to mark the successors blocks that follow as live.  If this is
+  // header of the merge construct, the construct may be folded, but we will
+  // definitely need the merge label.  If it is not a construct, the terminator
+  // must be live, and the successor blocks will be marked as live when
+  // processing the terminator.
+  uint32_t merge_id = basic_block->MergeBlockIdIfAny();
+  if (merge_id == 0) {
+    AddToWorklist(basic_block->terminator());
+  } else {
+    AddToWorklist(context()->get_def_use_mgr()->GetDef(merge_id));
+  }
+
+  // Mark the structured control flow constructs that contains this block as
+  // live.  If |inst| is an instruction in the loop header, then it is part of
+  // the loop, so the loop construct must be live.  We exclude the label because
+  // it does not matter how many times it is executed.  This could be extended
+  // to more instructions, but we will need it for now.
+  if (inst->opcode() != SpvOpLabel)
+    MarkLoopConstructAsLiveIfLoopHeader(basic_block);
+
+  Instruction* next_branch_inst = GetBranchForNextHeader(basic_block);
+  if (next_branch_inst != nullptr) {
+    AddToWorklist(next_branch_inst);
+    Instruction* mergeInst = GetMergeInstruction(next_branch_inst);
+    AddToWorklist(mergeInst);
+  }
+
+  if (inst->opcode() == SpvOpLoopMerge ||
+      inst->opcode() == SpvOpSelectionMerge) {
+    AddBreaksAndContinuesToWorklist(inst);
+  }
+}
+void AggressiveDCEPass::MarkLoopConstructAsLiveIfLoopHeader(
+    BasicBlock* basic_block) {
+  // If this is the header for a loop, then loop structure needs to keep as well
+  // because the loop header is also part of the loop.
+  Instruction* merge_inst = basic_block->GetLoopMergeInst();
+  if (merge_inst != nullptr) {
+    AddToWorklist(basic_block->terminator());
+    AddToWorklist(merge_inst);
+  }
+}
+
+void AggressiveDCEPass::AddOperandsToWorkList(const Instruction* inst) {
+  inst->ForEachInId([this](const uint32_t* iid) {
+    Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
+    AddToWorklist(inInst);
+  });
+  if (inst->type_id() != 0) {
+    AddToWorklist(get_def_use_mgr()->GetDef(inst->type_id()));
+  }
+}
+
+void AggressiveDCEPass::InitializeWorkList(
+    Function* func, std::list<BasicBlock*>& structured_order) {
+  AddToWorklist(&func->DefInst());
+  MarkFunctionParameterAsLive(func);
+  MarkFirstBlockAsLive(func);
+
+  // Add instructions with external side effects to the worklist. Also add
+  // branches that are not attached to a structured construct.
+  // TODO(s-perron): The handling of branch seems to be adhoc.  This needs to be
+  // cleaned up.
+  for (auto& bi : structured_order) {
+    for (auto ii = bi->begin(); ii != bi->end(); ++ii) {
+      SpvOp op = ii->opcode();
+      if (ii->IsBranch()) {
+        continue;
+      }
+      switch (op) {
+        case SpvOpStore: {
+          uint32_t var_id = 0;
+          (void)GetPtr(&*ii, &var_id);
+          if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii);
+        } break;
+        case SpvOpCopyMemory:
+        case SpvOpCopyMemorySized: {
+          uint32_t var_id = 0;
+          uint32_t target_addr_id =
+              ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx);
+          (void)GetPtr(target_addr_id, &var_id);
+          if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii);
+        } break;
+        case SpvOpLoopMerge:
+        case SpvOpSelectionMerge:
+        case SpvOpUnreachable:
+          break;
+        default: {
+          // Function calls, atomics, function params, function returns, etc.
+          if (!ii->IsOpcodeSafeToDelete()) {
+            AddToWorklist(&*ii);
+          }
+        } break;
+      }
+    }
+  }
+}
+
 void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
   // Keep all execution modes.
   for (auto& exec : get_module()->execution_modes()) {
@@ -603,7 +569,8 @@
   }
   // Keep all entry points.
   for (auto& entry : get_module()->entry_points()) {
-    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+    if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4) &&
+        !preserve_interface_) {
       // In SPIR-V 1.4 and later, entry points must list all global variables
       // used. DCE can still remove non-input/output variables and update the
       // interface list. Mark the entry point as live and inputs and outputs as
@@ -650,16 +617,25 @@
   }
 
   // For each DebugInfo GlobalVariable keep all operands except the Variable.
-  // Later, if the variable is dead, we will set the operand to DebugInfoNone.
+  // Later, if the variable is killed with KillInst(), we will set the operand
+  // to DebugInfoNone. Create and save DebugInfoNone now for this possible
+  // later use. This is slightly unoptimal, but it avoids generating it during
+  // instruction killing when the module is not consistent.
+  bool debug_global_seen = false;
   for (auto& dbg : get_module()->ext_inst_debuginfo()) {
-    if (dbg.GetOpenCL100DebugOpcode() != OpenCLDebugInfo100DebugGlobalVariable)
+    if (dbg.GetCommonDebugOpcode() != CommonDebugInfoDebugGlobalVariable)
       continue;
+    debug_global_seen = true;
     dbg.ForEachInId([this](const uint32_t* iid) {
-      Instruction* inInst = get_def_use_mgr()->GetDef(*iid);
-      if (inInst->opcode() == SpvOpVariable) return;
-      AddToWorklist(inInst);
+      Instruction* in_inst = get_def_use_mgr()->GetDef(*iid);
+      if (in_inst->opcode() == SpvOpVariable) return;
+      AddToWorklist(in_inst);
     });
   }
+  if (debug_global_seen) {
+    auto dbg_none = context()->get_debug_info_mgr()->GetDebugInfoNone();
+    AddToWorklist(dbg_none);
+  }
 }
 
 Pass::Status AggressiveDCEPass::ProcessImpl() {
@@ -691,7 +667,7 @@
 
   // Process all entry point functions.
   ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); };
-  modified |= context()->ProcessEntryPointCallTree(pfn);
+  modified |= context()->ProcessReachableCallTree(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
@@ -718,21 +694,20 @@
 
   // Cleanup all CFG including all unreachable blocks.
   ProcessFunction cleanup = [this](Function* f) { return CFGCleanup(f); };
-  modified |= context()->ProcessEntryPointCallTree(cleanup);
+  modified |= context()->ProcessReachableCallTree(cleanup);
 
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
 bool AggressiveDCEPass::EliminateDeadFunctions() {
   // Identify live functions first. Those that are not live
-  // are dead. ADCE is disabled for non-shaders so we do not check for exported
-  // functions here.
+  // are dead.
   std::unordered_set<const Function*> live_function_set;
   ProcessFunction mark_live = [&live_function_set](Function* fp) {
     live_function_set.insert(fp);
     return false;
   };
-  context()->ProcessEntryPointCallTree(mark_live);
+  context()->ProcessReachableCallTree(mark_live);
 
   bool modified = false;
   for (auto funcIter = get_module()->begin();
@@ -798,7 +773,7 @@
             uint32_t counter_buffer_id = annotation->GetSingleWordInOperand(2);
             Instruction* counter_buffer_inst =
                 get_def_use_mgr()->GetDef(counter_buffer_id);
-            if (IsDead(counter_buffer_inst)) {
+            if (!IsLive(counter_buffer_inst)) {
               context()->KillInst(annotation);
               modified = true;
             }
@@ -813,7 +788,7 @@
         for (uint32_t i = 1; i < annotation->NumOperands();) {
           Instruction* opInst =
               get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
-          if (IsDead(opInst)) {
+          if (!IsLive(opInst)) {
             // Don't increment |i|.
             annotation->RemoveOperand(i);
             modified = true;
@@ -840,7 +815,7 @@
         for (uint32_t i = 1; i < annotation->NumOperands();) {
           Instruction* opInst =
               get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
-          if (IsDead(opInst)) {
+          if (!IsLive(opInst)) {
             // Don't increment |i|.
             annotation->RemoveOperand(i + 1);
             annotation->RemoveOperand(i);
@@ -874,14 +849,13 @@
   }
 
   for (auto& dbg : get_module()->ext_inst_debuginfo()) {
-    if (!IsDead(&dbg)) continue;
+    if (IsLive(&dbg)) continue;
     // Save GlobalVariable if its variable is live, otherwise null out variable
     // index
-    if (dbg.GetOpenCL100DebugOpcode() ==
-        OpenCLDebugInfo100DebugGlobalVariable) {
+    if (dbg.GetCommonDebugOpcode() == CommonDebugInfoDebugGlobalVariable) {
       auto var_id = dbg.GetSingleWordOperand(kGlobalVariableVariableIndex);
       Instruction* var_inst = get_def_use_mgr()->GetDef(var_id);
-      if (!IsDead(var_inst)) continue;
+      if (IsLive(var_inst)) continue;
       context()->ForgetUses(&dbg);
       dbg.SetOperand(
           kGlobalVariableVariableIndex,
@@ -896,7 +870,7 @@
   // Since ADCE is disabled for non-shaders, we don't check for export linkage
   // attributes here.
   for (auto& val : get_module()->types_values()) {
-    if (IsDead(&val)) {
+    if (!IsLive(&val)) {
       // Save forwarded pointer if pointer is live since closure does not mark
       // this live as it does not have a result id. This is a little too
       // conservative since it is not known if the structure type that needed
@@ -904,14 +878,15 @@
       if (val.opcode() == SpvOpTypeForwardPointer) {
         uint32_t ptr_ty_id = val.GetSingleWordInOperand(0);
         Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id);
-        if (!IsDead(ptr_ty_inst)) continue;
+        if (IsLive(ptr_ty_inst)) continue;
       }
       to_kill_.push_back(&val);
       modified = true;
     }
   }
 
-  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
+  if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4) &&
+      !preserve_interface_) {
     // Remove the dead interface variables from the entry point interface list.
     for (auto& entry : get_module()->entry_points()) {
       std::vector<Operand> new_operands;
@@ -922,7 +897,7 @@
         } else {
           auto* var =
               get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
-          if (!IsDead(var)) {
+          if (IsLive(var)) {
             new_operands.push_back(entry.GetInOperand(i));
           }
         }
@@ -937,8 +912,6 @@
   return modified;
 }
 
-AggressiveDCEPass::AggressiveDCEPass() = default;
-
 Pass::Status AggressiveDCEPass::Process() {
   // Initialize extensions allowlist
   InitExtensions();
@@ -996,8 +969,122 @@
       "SPV_EXT_physical_storage_buffer",
       "SPV_KHR_terminate_invocation",
       "SPV_KHR_shader_clock",
+      "SPV_KHR_vulkan_memory_model",
+      "SPV_KHR_subgroup_uniform_control_flow",
+      "SPV_KHR_integer_dot_product",
+      "SPV_EXT_shader_image_int64",
+      "SPV_KHR_non_semantic_info",
   });
 }
 
+Instruction* AggressiveDCEPass::GetHeaderBranch(BasicBlock* blk) {
+  if (blk == nullptr) {
+    return nullptr;
+  }
+  BasicBlock* header_block = GetHeaderBlock(blk);
+  if (header_block == nullptr) {
+    return nullptr;
+  }
+  return header_block->terminator();
+}
+
+BasicBlock* AggressiveDCEPass::GetHeaderBlock(BasicBlock* blk) const {
+  if (blk == nullptr) {
+    return nullptr;
+  }
+
+  BasicBlock* header_block = nullptr;
+  if (blk->IsLoopHeader()) {
+    header_block = blk;
+  } else {
+    uint32_t header =
+        context()->GetStructuredCFGAnalysis()->ContainingConstruct(blk->id());
+    header_block = context()->get_instr_block(header);
+  }
+  return header_block;
+}
+
+Instruction* AggressiveDCEPass::GetMergeInstruction(Instruction* inst) {
+  BasicBlock* bb = context()->get_instr_block(inst);
+  if (bb == nullptr) {
+    return nullptr;
+  }
+  return bb->GetMergeInst();
+}
+
+Instruction* AggressiveDCEPass::GetBranchForNextHeader(BasicBlock* blk) {
+  if (blk == nullptr) {
+    return nullptr;
+  }
+
+  if (blk->IsLoopHeader()) {
+    uint32_t header =
+        context()->GetStructuredCFGAnalysis()->ContainingConstruct(blk->id());
+    blk = context()->get_instr_block(header);
+  }
+  return GetHeaderBranch(blk);
+}
+
+void AggressiveDCEPass::MarkFunctionParameterAsLive(const Function* func) {
+  func->ForEachParam(
+      [this](const Instruction* param) {
+        AddToWorklist(const_cast<Instruction*>(param));
+      },
+      false);
+}
+
+bool AggressiveDCEPass::BlockIsInConstruct(BasicBlock* header_block,
+                                           BasicBlock* bb) {
+  if (bb == nullptr || header_block == nullptr) {
+    return false;
+  }
+
+  uint32_t current_header = bb->id();
+  while (current_header != 0) {
+    if (current_header == header_block->id()) return true;
+    current_header = context()->GetStructuredCFGAnalysis()->ContainingConstruct(
+        current_header);
+  }
+  return false;
+}
+
+bool AggressiveDCEPass::IsEntryPointWithNoCalls(Function* func) {
+  auto cached_result = entry_point_with_no_calls_cache_.find(func->result_id());
+  if (cached_result != entry_point_with_no_calls_cache_.end()) {
+    return cached_result->second;
+  }
+  bool result = IsEntryPoint(func) && !HasCall(func);
+  entry_point_with_no_calls_cache_[func->result_id()] = result;
+  return result;
+}
+
+bool AggressiveDCEPass::IsEntryPoint(Function* func) {
+  for (const Instruction& entry_point : get_module()->entry_points()) {
+    uint32_t entry_point_id =
+        entry_point.GetSingleWordInOperand(kEntryPointFunctionIdInIdx);
+    if (entry_point_id == func->result_id()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool AggressiveDCEPass::HasCall(Function* func) {
+  return !func->WhileEachInst(
+      [](Instruction* inst) { return inst->opcode() != SpvOpFunctionCall; });
+}
+
+void AggressiveDCEPass::MarkFirstBlockAsLive(Function* func) {
+  BasicBlock* first_block = &*func->begin();
+  MarkBlockAsLive(first_block->GetLabelInst());
+}
+
+void AggressiveDCEPass::AddUnreachable(BasicBlock*& block) {
+  InstructionBuilder builder(
+      context(), block,
+      IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse);
+  builder.AddUnreachable();
+}
+
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index f02e729..1b3fd1e 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -44,7 +44,9 @@
   using GetBlocksFunction =
       std::function<std::vector<BasicBlock*>*(const BasicBlock*)>;
 
-  AggressiveDCEPass();
+  AggressiveDCEPass(bool preserve_interface = false)
+      : preserve_interface_(preserve_interface) {}
+
   const char* name() const override { return "eliminate-dead-code-aggressive"; }
   Status Process() override;
 
@@ -55,23 +57,26 @@
   }
 
  private:
+  // Preserve entry point interface if true. All variables in interface
+  // will be marked live and will not be eliminated. This mode is needed by
+  // GPU-Assisted Validation instrumentation where a change in the interface
+  // is not allowed.
+  bool preserve_interface_;
+
   // Return true if |varId| is a variable of |storageClass|. |varId| must either
   // be 0 or the result of an instruction.
   bool IsVarOfStorage(uint32_t varId, uint32_t storageClass);
 
-  // Return true if |varId| is variable of function storage class or is
-  // private variable and privates can be optimized like locals (see
-  // privates_like_local_).
-  bool IsLocalVar(uint32_t varId);
+  // Return true if the instance of the variable |varId| can only be access in
+  // |func|.  For example, a function scope variable, or a private variable
+  // where |func| is an entry point with no function calls.
+  bool IsLocalVar(uint32_t varId, Function* func);
 
   // Return true if |inst| is marked live.
   bool IsLive(const Instruction* inst) const {
     return live_insts_.Get(inst->unique_id());
   }
 
-  // Returns true if |inst| is dead.
-  bool IsDead(Instruction* inst);
-
   // Adds entry points, execution modes and workgroup size decorations to the
   // worklist for processing with the first function.
   void InitializeModuleScopeLiveInstructions();
@@ -101,18 +106,6 @@
   // If |varId| is local, mark all stores of varId as live.
   void ProcessLoad(Function* func, uint32_t varId);
 
-  // If |bp| is structured header block, returns true and sets |mergeInst| to
-  // the merge instruction, |branchInst| to the branch and |mergeBlockId| to the
-  // merge block if they are not nullptr.  Any of |mergeInst|, |branchInst| or
-  // |mergeBlockId| may be a null pointer.  Returns false if |bp| is a null
-  // pointer.
-  bool IsStructuredHeader(BasicBlock* bp, Instruction** mergeInst,
-                          Instruction** branchInst, uint32_t* mergeBlockId);
-
-  // Initialize block2headerBranch_,  header2nextHeaderBranch_, and
-  // branch2merge_ using |structuredOrder| to order blocks.
-  void ComputeBlock2HeaderMaps(std::list<BasicBlock*>& structuredOrder);
-
   // Add branch to |labelId| to end of block |bp|.
   void AddBranch(uint32_t labelId, BasicBlock* bp);
 
@@ -140,14 +133,100 @@
 
   Pass::Status ProcessImpl();
 
-  // True if current function has a call instruction contained in it
-  bool call_in_func_;
+  // Adds instructions which must be kept because of they have side-effects
+  // that ADCE cannot model to the work list.
+  void InitializeWorkList(Function* func,
+                          std::list<BasicBlock*>& structured_order);
 
-  // True if current function is an entry point
-  bool func_is_entry_point_;
+  // Process each instruction in the work list by marking any instruction that
+  // that it depends on as live, and adding it to the work list.  The work list
+  // will be empty at the end.
+  void ProcessWorkList(Function* func);
 
-  // True if current function is entry point and has no function calls.
-  bool private_like_local_;
+  // Kills any instructions in |func| that have not been marked as live.
+  bool KillDeadInstructions(const Function* func,
+                            std::list<BasicBlock*>& structured_order);
+
+  // Adds the instructions that define the operands of |inst| to the work list.
+  void AddOperandsToWorkList(const Instruction* inst);
+
+  // Marks all of the labels and branch that inst requires as live.
+  void MarkBlockAsLive(Instruction* inst);
+
+  // Marks any variables from which |inst| may require data as live.
+  void MarkLoadedVariablesAsLive(Function* func, Instruction* inst);
+
+  // Returns the id of the variable that |ptr_id| point to.  |ptr_id| must be a
+  // value whose type is a pointer.
+  uint32_t GetVariableId(uint32_t ptr_id);
+
+  // Returns all of the ids for the variables from which |inst| will load data.
+  std::vector<uint32_t> GetLoadedVariables(Instruction* inst);
+
+  // Returns all of the ids for the variables from which |inst| will load data.
+  // The opcode of |inst| must be  OpFunctionCall.
+  std::vector<uint32_t> GetLoadedVariablesFromFunctionCall(
+      const Instruction* inst);
+
+  // Returns the id of the variable from which |inst| will load data. |inst|
+  // must not be an OpFunctionCall.  Returns 0 if no data is read or the
+  // variable cannot be determined.  Note that in logical addressing mode the
+  // latter is not possible for function and private storage class because there
+  // cannot be variable pointers pointing to those storage classes.
+  uint32_t GetLoadedVariableFromNonFunctionCalls(Instruction* inst);
+
+  // Adds all decorations of |inst| to the work list.
+  void AddDecorationsToWorkList(const Instruction* inst);
+
+  // Adds all debug instruction associated with |inst| to the work list.
+  void AddDebugInstructionsToWorkList(const Instruction* inst);
+
+  // Marks all of the OpFunctionParameter instructions in |func| as live.
+  void MarkFunctionParameterAsLive(const Function* func);
+
+  // Returns the terminator instruction in the header for the innermost
+  // construct that contains |blk|.  Returns nullptr if no such header exists.
+  Instruction* GetHeaderBranch(BasicBlock* blk);
+
+  // Returns the header for the innermost construct that contains |blk|.  A loop
+  // header will be its own header.  Returns nullptr if no such header exists.
+  BasicBlock* GetHeaderBlock(BasicBlock* blk) const;
+
+  // Returns the same as |GetHeaderBlock| except if |blk| is a loop header it
+  // will return the header of the next enclosing construct.  Returns nullptr if
+  // no such header exists.
+  Instruction* GetBranchForNextHeader(BasicBlock* blk);
+
+  // Returns the merge instruction in the same basic block as |inst|.  Returns
+  // nullptr if one does not exist.
+  Instruction* GetMergeInstruction(Instruction* inst);
+
+  // Returns true if |bb| is in the construct with header |header_block|.
+  bool BlockIsInConstruct(BasicBlock* header_block, BasicBlock* bb);
+
+  // Returns true if |func| is an entry point that does not have any function
+  // calls.
+  bool IsEntryPointWithNoCalls(Function* func);
+
+  // Returns true if |func| is an entry point.
+  bool IsEntryPoint(Function* func);
+
+  // Returns true if |func| contains a function call.
+  bool HasCall(Function* func);
+
+  // Marks the first block, which is the entry block, in |func| as live.
+  void MarkFirstBlockAsLive(Function* func);
+
+  // Adds an OpUnreachable instruction at the end of |block|.
+  void AddUnreachable(BasicBlock*& block);
+
+  // Marks the OpLoopMerge and the terminator in |basic_block| as live if
+  // |basic_block| is a loop header.
+  void MarkLoopConstructAsLiveIfLoopHeader(BasicBlock* basic_block);
+
+  // The cached results for |IsEntryPointWithNoCalls|.  It maps the function's
+  // result id to the return value.
+  std::unordered_map<uint32_t, bool> entry_point_with_no_calls_cache_;
 
   // Live Instruction Worklist.  An instruction is added to this list
   // if it might have a side effect, either directly or indirectly.
@@ -156,27 +235,6 @@
   // building up the live instructions set |live_insts_|.
   std::queue<Instruction*> worklist_;
 
-  // Map from block to the branch instruction in the header of the most
-  // immediate controlling structured if or loop.  A loop header block points
-  // to its own branch instruction.  An if-selection block points to the branch
-  // of an enclosing construct's header, if one exists.
-  std::unordered_map<BasicBlock*, Instruction*> block2headerBranch_;
-
-  // Map from header block to the branch instruction in the header of the
-  // structured construct enclosing it.
-  // The liveness algorithm is designed to iteratively mark as live all
-  // structured constructs enclosing a live instruction.
-  std::unordered_map<BasicBlock*, Instruction*> header2nextHeaderBranch_;
-
-  // Maps basic block to their index in the structured order traversal.
-  std::unordered_map<BasicBlock*, uint32_t> structured_order_index_;
-
-  // Map from branch to its associated merge instruction, if any
-  std::unordered_map<Instruction*, Instruction*> branch2merge_;
-
-  // Store instructions to variables of private storage
-  std::vector<Instruction*> private_stores_;
-
   // Live Instructions
   utils::BitVector live_insts_;
 
diff --git a/source/opt/block_merge_pass.cpp b/source/opt/block_merge_pass.cpp
index c7315ba..ef7f31f 100644
--- a/source/opt/block_merge_pass.cpp
+++ b/source/opt/block_merge_pass.cpp
@@ -28,7 +28,9 @@
 bool BlockMergePass::MergeBlocks(Function* func) {
   bool modified = false;
   for (auto bi = func->begin(); bi != func->end();) {
-    if (blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
+    // Don't bother trying to merge unreachable blocks.
+    if (context()->IsReachable(*bi) &&
+        blockmergeutil::CanMergeWithSuccessor(context(), &*bi)) {
       blockmergeutil::MergeWithSuccessor(context(), func, bi);
       // Reprocess block.
       modified = true;
@@ -42,7 +44,7 @@
 Pass::Status BlockMergePass::Process() {
   // Process all entry point functions.
   ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
+  bool modified = context()->ProcessReachableCallTree(pfn);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp
index 14b5d36..8ae8020 100644
--- a/source/opt/block_merge_util.cpp
+++ b/source/opt/block_merge_util.cpp
@@ -103,11 +103,6 @@
     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)) {
@@ -176,10 +171,17 @@
       // and OpBranchConditional.
       auto terminator = bi->terminator();
       auto& vec = terminator->dbg_line_insts();
-      auto& new_vec = merge_inst->dbg_line_insts();
-      new_vec.insert(new_vec.end(), vec.begin(), vec.end());
-      terminator->clear_dbg_line_insts();
-
+      if (vec.size() > 0) {
+        merge_inst->ClearDbgLineInsts();
+        auto& new_vec = merge_inst->dbg_line_insts();
+        new_vec.insert(new_vec.end(), vec.begin(), vec.end());
+        terminator->ClearDbgLineInsts();
+        for (auto& l_inst : new_vec)
+          context->get_def_use_mgr()->AnalyzeInstDefUse(&l_inst);
+      }
+      // Clear debug scope of terminator to avoid DebugScope
+      // emitted between terminator and merge.
+      terminator->SetDebugScope(DebugScope(kNoDebugScope, kNoInlinedAt));
       // Move the merge instruction to just before the terminator.
       merge_inst->InsertBefore(terminator);
     }
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index d84f13f..8b896d5 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -291,6 +291,10 @@
 }
 
 bool CCPPass::PropagateConstants(Function* fp) {
+  if (fp->IsDeclaration()) {
+    return false;
+  }
+
   // Mark function parameters as varying.
   fp->ForEachParam([this](const Instruction* inst) {
     values_[inst->result_id()] = kVaryingSSAId;
diff --git a/source/opt/code_sink.cpp b/source/opt/code_sink.cpp
index e49029f..cd77797 100644
--- a/source/opt/code_sink.cpp
+++ b/source/opt/code_sink.cpp
@@ -218,8 +218,10 @@
       case SpvOpAtomicISub:
       case SpvOpAtomicSMin:
       case SpvOpAtomicUMin:
+      case SpvOpAtomicFMinEXT:
       case SpvOpAtomicSMax:
       case SpvOpAtomicUMax:
+      case SpvOpAtomicFMaxEXT:
       case SpvOpAtomicAnd:
       case SpvOpAtomicOr:
       case SpvOpAtomicXor:
diff --git a/source/opt/combine_access_chains.cpp b/source/opt/combine_access_chains.cpp
index facfc24..142897a 100644
--- a/source/opt/combine_access_chains.cpp
+++ b/source/opt/combine_access_chains.cpp
@@ -34,6 +34,10 @@
 }
 
 bool CombineAccessChains::ProcessFunction(Function& function) {
+  if (function.IsDeclaration()) {
+    return false;
+  }
+
   bool modified = false;
 
   cfg()->ForEachBlockInReversePostOrder(
diff --git a/source/opt/compact_ids_pass.cpp b/source/opt/compact_ids_pass.cpp
index 6709153..8815b8c 100644
--- a/source/opt/compact_ids_pass.cpp
+++ b/source/opt/compact_ids_pass.cpp
@@ -86,9 +86,12 @@
       },
       true);
 
-  if (modified)
+  if (modified) {
     context()->module()->SetIdBound(
         static_cast<uint32_t>(result_id_mapping.size() + 1));
+    // There are ids in the feature manager that could now be invalid
+    context()->ResetFeatureManager();
+  }
 
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
diff --git a/source/opt/const_folding_rules.cpp b/source/opt/const_folding_rules.cpp
index d262a7e..515a3ed 100644
--- a/source/opt/const_folding_rules.cpp
+++ b/source/opt/const_folding_rules.cpp
@@ -1002,7 +1002,7 @@
          "Expecting a GLSLstd450 extended instruction.");
 
   // Make sure all Clamp operands are constants.
-  for (uint32_t i = 1; i < 3; i++) {
+  for (uint32_t i = 1; i < 4; i++) {
     if (constants[i] == nullptr) {
       return nullptr;
     }
@@ -1017,7 +1017,7 @@
                         context);
 }
 
-// Fold a clamp instruction when |x >= min_val|.
+// Fold a clamp instruction when |x <= min_val|.
 const analysis::Constant* FoldClamp2(
     IRContext* context, Instruction* inst,
     const std::vector<const analysis::Constant*>& constants) {
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index cf24295..020e248 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -217,7 +217,8 @@
   auto* new_inst_ptr = new_inst.get();
   *pos = pos->InsertBefore(std::move(new_inst));
   ++(*pos);
-  context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr);
+  if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
+    context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr);
   MapConstantToInst(new_const, new_inst_ptr);
   return new_inst_ptr;
 }
@@ -389,6 +390,36 @@
   return cst ? RegisterConstant(std::move(cst)) : nullptr;
 }
 
+const Constant* ConstantManager::GetNumericVectorConstantWithWords(
+    const Vector* type, const std::vector<uint32_t>& literal_words) {
+  const auto* element_type = type->element_type();
+  uint32_t words_per_element = 0;
+  if (const auto* float_type = element_type->AsFloat())
+    words_per_element = float_type->width() / 32;
+  else if (const auto* int_type = element_type->AsInteger())
+    words_per_element = int_type->width() / 32;
+
+  if (words_per_element != 1 && words_per_element != 2) return nullptr;
+
+  if (words_per_element * type->element_count() !=
+      static_cast<uint32_t>(literal_words.size())) {
+    return nullptr;
+  }
+
+  std::vector<uint32_t> element_ids;
+  for (uint32_t i = 0; i < type->element_count(); ++i) {
+    auto first_word = literal_words.begin() + (words_per_element * i);
+    std::vector<uint32_t> const_data(first_word,
+                                     first_word + words_per_element);
+    const analysis::Constant* element_constant =
+        GetConstant(element_type, const_data);
+    auto element_id = GetDefiningInstruction(element_constant)->result_id();
+    element_ids.push_back(element_id);
+  }
+
+  return GetConstant(type, element_ids);
+}
+
 uint32_t ConstantManager::GetFloatConst(float val) {
   Type* float_type = context()->get_type_mgr()->GetFloatType();
   utils::FloatProxy<float> v(val);
@@ -402,6 +433,12 @@
   return GetDefiningInstruction(c)->result_id();
 }
 
+uint32_t ConstantManager::GetUIntConst(uint32_t val) {
+  Type* uint_type = context()->get_type_mgr()->GetUIntType();
+  const Constant* c = GetConstant(uint_type, {val});
+  return GetDefiningInstruction(c)->result_id();
+}
+
 std::vector<const analysis::Constant*> Constant::GetVectorComponents(
     analysis::ConstantManager* const_mgr) const {
   std::vector<const analysis::Constant*> components;
diff --git a/source/opt/constants.h b/source/opt/constants.h
index e17ae6b..52bd809 100644
--- a/source/opt/constants.h
+++ b/source/opt/constants.h
@@ -58,7 +58,7 @@
 class Constant {
  public:
   Constant() = delete;
-  virtual ~Constant() {}
+  virtual ~Constant() = default;
 
   // Make a deep copy of this constant.
   virtual std::unique_ptr<Constant> Copy() const = 0;
@@ -506,10 +506,11 @@
   IRContext* context() const { return ctx_; }
 
   // Gets or creates a unique Constant instance of type |type| and a vector of
-  // constant defining words |words|. If a Constant instance existed already in
-  // the constant pool, it returns a pointer to it.  Otherwise, it creates one
-  // using CreateConstant. If a new Constant instance cannot be created, it
-  // returns nullptr.
+  // constant defining words or ids for elements of Vector type
+  // |literal_words_or_ids|. If a Constant instance existed already in the
+  // constant pool, it returns a pointer to it. Otherwise, it creates one using
+  // CreateConstant. If a new Constant instance cannot be created, it returns
+  // nullptr.
   const Constant* GetConstant(
       const Type* type, const std::vector<uint32_t>& literal_words_or_ids);
 
@@ -519,6 +520,14 @@
                                                    literal_words_or_ids.end()));
   }
 
+  // Gets or creates a unique Constant instance of Vector type |type| with
+  // numeric elements and a vector of constant defining words |literal_words|.
+  // If a Constant instance existed already in the constant pool, it returns a
+  // pointer to it. Otherwise, it creates one using CreateConstant. If a new
+  // Constant instance cannot be created, it returns nullptr.
+  const Constant* GetNumericVectorConstantWithWords(
+      const Vector* type, const std::vector<uint32_t>& literal_words);
+
   // Gets or creates a Constant instance to hold the constant value of the given
   // instruction. It returns a pointer to a Constant instance or nullptr if it
   // could not create the constant.
@@ -633,6 +642,9 @@
   // Returns the id of a 32-bit signed integer constant with value |val|.
   uint32_t GetSIntConst(int32_t val);
 
+  // Returns the id of a 32-bit unsigned integer constant with value |val|.
+  uint32_t GetUIntConst(uint32_t val);
+
  private:
   // Creates a Constant instance with the given type and a vector of constant
   // defining words. Returns a unique pointer to the created Constant instance
diff --git a/source/opt/control_dependence.cpp b/source/opt/control_dependence.cpp
new file mode 100644
index 0000000..f4879e0
--- /dev/null
+++ b/source/opt/control_dependence.cpp
@@ -0,0 +1,156 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/control_dependence.h"
+
+#include <cassert>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "source/opt/basic_block.h"
+#include "source/opt/cfg.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/function.h"
+#include "source/opt/instruction.h"
+#include "spirv/unified1/spirv.h"
+
+// Computes the control dependence graph (CDG) using the algorithm in Cytron
+// 1991, "Efficiently Computing Static Single Assignment Form and the Control
+// Dependence Graph." It relies on the fact that the control dependence sources
+// (blocks on which a block is control dependent) are exactly the post-dominance
+// frontier for that block. The explanation and proofs are given in Section 6 of
+// that paper.
+// Link: https://www.cs.utexas.edu/~pingali/CS380C/2010/papers/ssaCytron.pdf
+//
+// The algorithm in Section 4.2 of the same paper is used to construct the
+// dominance frontier. It uses the post-dominance tree, which is available in
+// the IR context.
+
+namespace spvtools {
+namespace opt {
+constexpr uint32_t ControlDependenceAnalysis::kPseudoEntryBlock;
+
+uint32_t ControlDependence::GetConditionID(const CFG& cfg) const {
+  if (source_bb_id() == 0) {
+    // Entry dependence; return 0.
+    return 0;
+  }
+  const BasicBlock* source_bb = cfg.block(source_bb_id());
+  const Instruction* branch = source_bb->terminator();
+  assert((branch->opcode() == SpvOpBranchConditional ||
+          branch->opcode() == SpvOpSwitch) &&
+         "invalid control dependence; last instruction must be conditional "
+         "branch or switch");
+  return branch->GetSingleWordInOperand(0);
+}
+
+bool ControlDependence::operator<(const ControlDependence& other) const {
+  return std::tie(source_bb_id_, target_bb_id_, branch_target_bb_id_) <
+         std::tie(other.source_bb_id_, other.target_bb_id_,
+                  other.branch_target_bb_id_);
+}
+
+bool ControlDependence::operator==(const ControlDependence& other) const {
+  return std::tie(source_bb_id_, target_bb_id_, branch_target_bb_id_) ==
+         std::tie(other.source_bb_id_, other.target_bb_id_,
+                  other.branch_target_bb_id_);
+}
+
+std::ostream& operator<<(std::ostream& os, const ControlDependence& dep) {
+  os << dep.source_bb_id() << "->" << dep.target_bb_id();
+  if (dep.branch_target_bb_id() != dep.target_bb_id()) {
+    os << " through " << dep.branch_target_bb_id();
+  }
+  return os;
+}
+
+void ControlDependenceAnalysis::ComputePostDominanceFrontiers(
+    const CFG& cfg, const PostDominatorAnalysis& pdom) {
+  // Compute post-dominance frontiers (reverse graph).
+  // The dominance frontier for a block X is equal to (Equation 4)
+  //   DF_local(X) U { B in DF_up(Z) | X = ipdom(Z) }
+  //   (ipdom(Z) is the immediate post-dominator of Z.)
+  // where
+  //   DF_local(X) = { Y | X -> Y in CFG, X does not strictly post-dominate Y }
+  //     represents the contribution of X's predecessors to the DF, and
+  //   DF_up(Z) = { Y | Y in DF(Z), ipdom(Z) does not strictly post-dominate Y }
+  //     (note: ipdom(Z) = X.)
+  //     represents the contribution of a block to its immediate post-
+  //     dominator's DF.
+  // This is computed in one pass through a post-order traversal of the
+  // post-dominator tree.
+
+  // Assert that there is a block other than the pseudo exit in the pdom tree,
+  // as we need one to get the function entry point (as the pseudo exit is not
+  // actually part of the function.)
+  assert(!cfg.IsPseudoExitBlock(pdom.GetDomTree().post_begin()->bb_));
+  Function* function = pdom.GetDomTree().post_begin()->bb_->GetParent();
+  uint32_t function_entry = function->entry()->id();
+  // Explicitly initialize pseudo-entry block, as it doesn't depend on anything,
+  // so it won't be initialized in the following loop.
+  reverse_nodes_[kPseudoEntryBlock] = {};
+  for (auto it = pdom.GetDomTree().post_cbegin();
+       it != pdom.GetDomTree().post_cend(); ++it) {
+    ComputePostDominanceFrontierForNode(cfg, pdom, function_entry, *it);
+  }
+}
+
+void ControlDependenceAnalysis::ComputePostDominanceFrontierForNode(
+    const CFG& cfg, const PostDominatorAnalysis& pdom, uint32_t function_entry,
+    const DominatorTreeNode& pdom_node) {
+  const uint32_t label = pdom_node.id();
+  ControlDependenceList& edges = reverse_nodes_[label];
+  for (uint32_t pred : cfg.preds(label)) {
+    if (!pdom.StrictlyDominates(label, pred)) {
+      edges.push_back(ControlDependence(pred, label));
+    }
+  }
+  if (label == function_entry) {
+    // Add edge from pseudo-entry to entry.
+    // In CDG construction, an edge is added from entry to exit, so only the
+    // exit node can post-dominate entry.
+    edges.push_back(ControlDependence(kPseudoEntryBlock, label));
+  }
+  for (DominatorTreeNode* child : pdom_node) {
+    // Note: iterate dependences by value, as we need a copy.
+    for (const ControlDependence& dep : reverse_nodes_[child->id()]) {
+      // Special-case pseudo-entry, as above.
+      if (dep.source_bb_id() == kPseudoEntryBlock ||
+          !pdom.StrictlyDominates(label, dep.source_bb_id())) {
+        edges.push_back(ControlDependence(dep.source_bb_id(), label,
+                                          dep.branch_target_bb_id()));
+      }
+    }
+  }
+}
+
+void ControlDependenceAnalysis::ComputeControlDependenceGraph(
+    const CFG& cfg, const PostDominatorAnalysis& pdom) {
+  ComputePostDominanceFrontiers(cfg, pdom);
+  ComputeForwardGraphFromReverse();
+}
+
+void ControlDependenceAnalysis::ComputeForwardGraphFromReverse() {
+  for (const auto& entry : reverse_nodes_) {
+    // Ensure an entry is created for each node.
+    forward_nodes_[entry.first];
+    for (const ControlDependence& dep : entry.second) {
+      forward_nodes_[dep.source_bb_id()].push_back(dep);
+    }
+  }
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/control_dependence.h b/source/opt/control_dependence.h
new file mode 100644
index 0000000..993f3793
--- /dev/null
+++ b/source/opt/control_dependence.h
@@ -0,0 +1,197 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_CONTROL_DEPENDENCE_H_
+#define SOURCE_OPT_CONTROL_DEPENDENCE_H_
+
+#include <algorithm>
+#include <cstdint>
+#include <functional>
+#include <ostream>
+#include <unordered_map>
+#include <vector>
+
+#include "source/opt/cfg.h"
+#include "source/opt/dominator_analysis.h"
+
+namespace spvtools {
+namespace opt {
+
+class ControlDependence {
+ public:
+  // The label of the source of this dependence, i.e. the block on which the
+  // target is dependent on.
+  // A |source_bb_id| of 0 represents an "entry" dependence, meaning that the
+  // execution of |target_bb_id| is only dependent on entry to the function.
+  uint32_t source_bb_id() const { return source_bb_id_; }
+  // The label of the target of this dependence, i.e. the block which is
+  // dependent on the source.
+  uint32_t target_bb_id() const { return target_bb_id_; }
+  // The label of the target of the *branch* for this dependence.
+  // Equal to the ID of the entry block for entry dependences.
+  //
+  // For example, for the partial CFG pictured below:
+  // 1 ---> 2 ---> 4 ---> 6
+  //  \      \            ^
+  //   \-> 3  \-> 5 -----/
+  // Block 6 is control dependent on block 1, but this dependence comes from the
+  // branch 1 -> 2, so in this case the branch target ID would be 2.
+  uint32_t branch_target_bb_id() const { return branch_target_bb_id_; }
+
+  // Create a direct control dependence from BB ID |source| to |target|.
+  ControlDependence(uint32_t source, uint32_t target)
+      : source_bb_id_(source),
+        target_bb_id_(target),
+        branch_target_bb_id_(target) {}
+  // Create a control dependence from BB ID |source| to |target| through the
+  // branch from |source| to |branch_target|.
+  ControlDependence(uint32_t source, uint32_t target, uint32_t branch_target)
+      : source_bb_id_(source),
+        target_bb_id_(target),
+        branch_target_bb_id_(branch_target) {}
+
+  // Gets the ID of the conditional value for the branch corresponding to this
+  // control dependence. This is the first input operand for both
+  // OpConditionalBranch and OpSwitch.
+  // Returns 0 for entry dependences.
+  uint32_t GetConditionID(const CFG& cfg) const;
+
+  bool operator==(const ControlDependence& other) const;
+  bool operator!=(const ControlDependence& other) const {
+    return !(*this == other);
+  }
+
+  // Comparison operators, ordered lexicographically. Total ordering.
+  bool operator<(const ControlDependence& other) const;
+  bool operator>(const ControlDependence& other) const { return other < *this; }
+  bool operator<=(const ControlDependence& other) const {
+    return !(*this > other);
+  }
+  bool operator>=(const ControlDependence& other) const {
+    return !(*this < other);
+  }
+
+ private:
+  uint32_t source_bb_id_;
+  uint32_t target_bb_id_;
+  uint32_t branch_target_bb_id_;
+};
+
+// Prints |dep| to |os| in a human-readable way. For example,
+//   1->2           (target_bb_id = branch_target_bb_id = 2)
+//   3->4 through 5 (target_bb_id = 4, branch_target_bb_id = 5)
+std::ostream& operator<<(std::ostream& os, const ControlDependence& dep);
+
+// Represents the control dependence graph. A basic block is control dependent
+// on another if the result of that block (e.g. the condition of a conditional
+// branch) influences whether it is executed or not. More formally, a block A is
+// control dependent on B iff:
+// 1. there exists a path from A to the exit node that does *not* go through B
+//    (i.e., A does not postdominate B), and
+// 2. there exists a path B -> b_1 -> ... -> b_n -> A such that A post-dominates
+//    all nodes b_i.
+class ControlDependenceAnalysis {
+ public:
+  // Map basic block labels to control dependencies/dependents.
+  // Not guaranteed to be in any particular order.
+  using ControlDependenceList = std::vector<ControlDependence>;
+  using ControlDependenceListMap =
+      std::unordered_map<uint32_t, ControlDependenceList>;
+
+  // 0, the label number for the pseudo entry block.
+  // All control dependences on the pseudo entry block are of type kEntry, and
+  // vice versa.
+  static constexpr uint32_t kPseudoEntryBlock = 0;
+
+  // Build the control dependence graph for the given control flow graph |cfg|
+  // and corresponding post-dominator analysis |pdom|.
+  void ComputeControlDependenceGraph(const CFG& cfg,
+                                     const PostDominatorAnalysis& pdom);
+
+  // Get the list of the nodes that depend on a block.
+  // Return value is not guaranteed to be in any particular order.
+  const ControlDependenceList& GetDependenceTargets(uint32_t block) const {
+    return forward_nodes_.at(block);
+  }
+
+  // Get the list of the nodes on which a block depends on.
+  // Return value is not guaranteed to be in any particular order.
+  const ControlDependenceList& GetDependenceSources(uint32_t block) const {
+    return reverse_nodes_.at(block);
+  }
+
+  // Runs the function |f| on each block label in the CDG. If any iteration
+  // returns false, immediately stops iteration and returns false. Otherwise
+  // returns true. Nodes are iterated in some undefined order, including the
+  // pseudo-entry block.
+  bool WhileEachBlockLabel(std::function<bool(uint32_t)> f) const {
+    for (const auto& entry : forward_nodes_) {
+      if (!f(entry.first)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // Runs the function |f| on each block label in the CDG. Nodes are iterated in
+  // some undefined order, including the pseudo-entry block.
+  void ForEachBlockLabel(std::function<void(uint32_t)> f) const {
+    WhileEachBlockLabel([&f](uint32_t label) {
+      f(label);
+      return true;
+    });
+  }
+
+  // Returns true if the block |id| exists in the control dependence graph.
+  // This can be false even if the block exists in the function when it is part
+  // of an infinite loop, since it is not part of the post-dominator tree.
+  bool HasBlock(uint32_t id) const { return forward_nodes_.count(id) > 0; }
+
+  // Returns true if block |a| is dependent on block |b|.
+  bool IsDependent(uint32_t a, uint32_t b) const {
+    if (!HasBlock(a)) return false;
+    // BBs tend to have more dependents (targets) than they are dependent on
+    // (sources), so search sources.
+    const ControlDependenceList& a_sources = GetDependenceSources(a);
+    return std::find_if(a_sources.begin(), a_sources.end(),
+                        [b](const ControlDependence& dep) {
+                          return dep.source_bb_id() == b;
+                        }) != a_sources.end();
+  }
+
+ private:
+  // Computes the post-dominance frontiers (i.e. the reverse CDG) for each node
+  // in the post-dominator tree. Only modifies reverse_nodes_; forward_nodes_ is
+  // not modified.
+  void ComputePostDominanceFrontiers(const CFG& cfg,
+                                     const PostDominatorAnalysis& pdom);
+  // Computes the post-dominance frontier for a specific node |pdom_node| in the
+  // post-dominator tree. Result is placed in reverse_nodes_[pdom_node.id()].
+  void ComputePostDominanceFrontierForNode(const CFG& cfg,
+                                           const PostDominatorAnalysis& pdom,
+                                           uint32_t function_entry,
+                                           const DominatorTreeNode& pdom_node);
+
+  // Computes the forward graph (forward_nodes_) from the reverse graph
+  // (reverse_nodes_).
+  void ComputeForwardGraphFromReverse();
+
+  ControlDependenceListMap forward_nodes_;
+  ControlDependenceListMap reverse_nodes_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_CONTROL_DEPENDENCE_H_
diff --git a/source/opt/convert_to_half_pass.cpp b/source/opt/convert_to_half_pass.cpp
index 5022e1b..0b1afd2 100644
--- a/source/opt/convert_to_half_pass.cpp
+++ b/source/opt/convert_to_half_pass.cpp
@@ -147,8 +147,8 @@
   return true;
 }
 
-void ConvertToHalfPass::RemoveRelaxedDecoration(uint32_t id) {
-  context()->get_decoration_mgr()->RemoveDecorationsFrom(
+bool ConvertToHalfPass::RemoveRelaxedDecoration(uint32_t id) {
+  return context()->get_decoration_mgr()->RemoveDecorationsFrom(
       id, [](const Instruction& dec) {
         if (dec.opcode() == SpvOpDecorate &&
             dec.GetSingleWordInOperand(1u) == SpvDecorationRelaxedPrecision)
@@ -340,14 +340,18 @@
   Pass::ProcessFunction pfn = [this](Function* fp) {
     return ProcessFunction(fp);
   };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
+  bool modified = context()->ProcessReachableCallTree(pfn);
   // If modified, make sure module has Float16 capability
   if (modified) context()->AddCapability(SpvCapabilityFloat16);
   // Remove all RelaxedPrecision decorations from instructions and globals
-  for (auto c_id : relaxed_ids_set_) RemoveRelaxedDecoration(c_id);
+  for (auto c_id : relaxed_ids_set_) {
+    modified |= RemoveRelaxedDecoration(c_id);
+  }
   for (auto& val : get_module()->types_values()) {
     uint32_t v_id = val.result_id();
-    if (v_id != 0) RemoveRelaxedDecoration(v_id);
+    if (v_id != 0) {
+      modified |= RemoveRelaxedDecoration(v_id);
+    }
   }
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
diff --git a/source/opt/convert_to_half_pass.h b/source/opt/convert_to_half_pass.h
index 143aebf..b647dd4 100644
--- a/source/opt/convert_to_half_pass.h
+++ b/source/opt/convert_to_half_pass.h
@@ -74,7 +74,7 @@
   void GenConvert(uint32_t* val_idp, uint32_t width, Instruction* inst);
 
   // Remove RelaxedPrecision decoration of |id|.
-  void RemoveRelaxedDecoration(uint32_t id);
+  bool RemoveRelaxedDecoration(uint32_t id);
 
   // Add |inst| to relaxed instruction set if warranted. Specifically, if
   // it is float32 and either decorated relaxed or a composite or phi
diff --git a/source/opt/convert_to_sampled_image_pass.cpp b/source/opt/convert_to_sampled_image_pass.cpp
new file mode 100644
index 0000000..e84d357
--- /dev/null
+++ b/source/opt/convert_to_sampled_image_pass.cpp
@@ -0,0 +1,437 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/convert_to_sampled_image_pass.h"
+
+#include <cctype>
+#include <cstring>
+#include <tuple>
+
+#include "source/opt/ir_builder.h"
+#include "source/util/make_unique.h"
+#include "source/util/parse_number.h"
+
+namespace spvtools {
+namespace opt {
+
+using VectorOfDescriptorSetAndBindingPairs =
+    std::vector<DescriptorSetAndBinding>;
+using DescriptorSetBindingToInstruction =
+    ConvertToSampledImagePass::DescriptorSetBindingToInstruction;
+
+namespace {
+
+using utils::ParseNumber;
+
+// Returns true if the given char is ':', '\0' or considered as blank space
+// (i.e.: '\n', '\r', '\v', '\t', '\f' and ' ').
+bool IsSeparator(char ch) {
+  return std::strchr(":\0", ch) || std::isspace(ch) != 0;
+}
+
+// Reads characters starting from |str| until it meets a separator. Parses a
+// number from the characters and stores it into |number|. Returns the pointer
+// to the separator if it succeeds. Otherwise, returns nullptr.
+const char* ParseNumberUntilSeparator(const char* str, uint32_t* number) {
+  const char* number_begin = str;
+  while (!IsSeparator(*str)) str++;
+  const char* number_end = str;
+  std::string number_in_str(number_begin, number_end - number_begin);
+  if (!utils::ParseNumber(number_in_str.c_str(), number)) {
+    // The descriptor set is not a valid uint32 number.
+    return nullptr;
+  }
+  return str;
+}
+
+// Returns id of the image type used for the sampled image type of
+// |sampled_image|.
+uint32_t GetImageTypeOfSampledImage(analysis::TypeManager* type_mgr,
+                                    Instruction* sampled_image) {
+  auto* sampled_image_type =
+      type_mgr->GetType(sampled_image->type_id())->AsSampledImage();
+  return type_mgr->GetTypeInstruction(sampled_image_type->image_type());
+}
+
+// Finds the instruction whose id is |inst_id|. Follows the operand of
+// OpCopyObject recursively if the opcode of the instruction is OpCopyObject
+// and returns the first instruction that does not have OpCopyObject as opcode.
+Instruction* GetNonCopyObjectDef(analysis::DefUseManager* def_use_mgr,
+                                 uint32_t inst_id) {
+  Instruction* inst = def_use_mgr->GetDef(inst_id);
+  while (inst->opcode() == SpvOpCopyObject) {
+    inst_id = inst->GetSingleWordInOperand(0u);
+    inst = def_use_mgr->GetDef(inst_id);
+  }
+  return inst;
+}
+
+}  // namespace
+
+bool ConvertToSampledImagePass::GetDescriptorSetBinding(
+    const Instruction& inst,
+    DescriptorSetAndBinding* descriptor_set_binding) const {
+  auto* decoration_manager = context()->get_decoration_mgr();
+  bool found_descriptor_set_to_convert = false;
+  bool found_binding_to_convert = false;
+  for (auto decorate :
+       decoration_manager->GetDecorationsFor(inst.result_id(), false)) {
+    uint32_t decoration = decorate->GetSingleWordInOperand(1u);
+    if (decoration == SpvDecorationDescriptorSet) {
+      if (found_descriptor_set_to_convert) {
+        assert(false && "A resource has two OpDecorate for the descriptor set");
+        return false;
+      }
+      descriptor_set_binding->descriptor_set =
+          decorate->GetSingleWordInOperand(2u);
+      found_descriptor_set_to_convert = true;
+    } else if (decoration == SpvDecorationBinding) {
+      if (found_binding_to_convert) {
+        assert(false && "A resource has two OpDecorate for the binding");
+        return false;
+      }
+      descriptor_set_binding->binding = decorate->GetSingleWordInOperand(2u);
+      found_binding_to_convert = true;
+    }
+  }
+  return found_descriptor_set_to_convert && found_binding_to_convert;
+}
+
+bool ConvertToSampledImagePass::ShouldResourceBeConverted(
+    const DescriptorSetAndBinding& descriptor_set_binding) const {
+  return descriptor_set_binding_pairs_.find(descriptor_set_binding) !=
+         descriptor_set_binding_pairs_.end();
+}
+
+const analysis::Type* ConvertToSampledImagePass::GetVariableType(
+    const Instruction& variable) const {
+  if (variable.opcode() != SpvOpVariable) return nullptr;
+  auto* type = context()->get_type_mgr()->GetType(variable.type_id());
+  auto* pointer_type = type->AsPointer();
+  if (!pointer_type) return nullptr;
+
+  return pointer_type->pointee_type();
+}
+
+SpvStorageClass ConvertToSampledImagePass::GetStorageClass(
+    const Instruction& variable) const {
+  assert(variable.opcode() == SpvOpVariable);
+  auto* type = context()->get_type_mgr()->GetType(variable.type_id());
+  auto* pointer_type = type->AsPointer();
+  if (!pointer_type) return SpvStorageClassMax;
+
+  return pointer_type->storage_class();
+}
+
+bool ConvertToSampledImagePass::CollectResourcesToConvert(
+    DescriptorSetBindingToInstruction* descriptor_set_binding_pair_to_sampler,
+    DescriptorSetBindingToInstruction* descriptor_set_binding_pair_to_image)
+    const {
+  for (auto& inst : context()->types_values()) {
+    const auto* variable_type = GetVariableType(inst);
+    if (variable_type == nullptr) continue;
+
+    DescriptorSetAndBinding descriptor_set_binding;
+    if (!GetDescriptorSetBinding(inst, &descriptor_set_binding)) continue;
+
+    if (!ShouldResourceBeConverted(descriptor_set_binding)) {
+      continue;
+    }
+
+    if (variable_type->AsImage()) {
+      if (!descriptor_set_binding_pair_to_image
+               ->insert({descriptor_set_binding, &inst})
+               .second) {
+        return false;
+      }
+    } else if (variable_type->AsSampler()) {
+      if (!descriptor_set_binding_pair_to_sampler
+               ->insert({descriptor_set_binding, &inst})
+               .second) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+Pass::Status ConvertToSampledImagePass::Process() {
+  Status status = Status::SuccessWithoutChange;
+
+  DescriptorSetBindingToInstruction descriptor_set_binding_pair_to_sampler,
+      descriptor_set_binding_pair_to_image;
+  if (!CollectResourcesToConvert(&descriptor_set_binding_pair_to_sampler,
+                                 &descriptor_set_binding_pair_to_image)) {
+    return Status::Failure;
+  }
+
+  for (auto& image : descriptor_set_binding_pair_to_image) {
+    status = CombineStatus(
+        status, UpdateImageVariableToSampledImage(image.second, image.first));
+    if (status == Status::Failure) {
+      return status;
+    }
+  }
+
+  for (const auto& sampler : descriptor_set_binding_pair_to_sampler) {
+    // Converting only a Sampler to Sampled Image is not allowed. It must have a
+    // corresponding image to combine the sampler with.
+    auto image_itr = descriptor_set_binding_pair_to_image.find(sampler.first);
+    if (image_itr == descriptor_set_binding_pair_to_image.end() ||
+        image_itr->second == nullptr) {
+      return Status::Failure;
+    }
+
+    status = CombineStatus(
+        status, CheckUsesOfSamplerVariable(sampler.second, image_itr->second));
+    if (status == Status::Failure) {
+      return status;
+    }
+  }
+
+  return status;
+}
+
+void ConvertToSampledImagePass::FindUses(const Instruction* inst,
+                                         std::vector<Instruction*>* uses,
+                                         uint32_t user_opcode) const {
+  auto* def_use_mgr = context()->get_def_use_mgr();
+  def_use_mgr->ForEachUser(inst, [uses, user_opcode, this](Instruction* user) {
+    if (user->opcode() == user_opcode) {
+      uses->push_back(user);
+    } else if (user->opcode() == SpvOpCopyObject) {
+      FindUses(user, uses, user_opcode);
+    }
+  });
+}
+
+void ConvertToSampledImagePass::FindUsesOfImage(
+    const Instruction* image, std::vector<Instruction*>* uses) const {
+  auto* def_use_mgr = context()->get_def_use_mgr();
+  def_use_mgr->ForEachUser(image, [uses, this](Instruction* user) {
+    switch (user->opcode()) {
+      case SpvOpImageFetch:
+      case SpvOpImageRead:
+      case SpvOpImageWrite:
+      case SpvOpImageQueryFormat:
+      case SpvOpImageQueryOrder:
+      case SpvOpImageQuerySizeLod:
+      case SpvOpImageQuerySize:
+      case SpvOpImageQueryLevels:
+      case SpvOpImageQuerySamples:
+      case SpvOpImageSparseFetch:
+        uses->push_back(user);
+      default:
+        break;
+    }
+    if (user->opcode() == SpvOpCopyObject) {
+      FindUsesOfImage(user, uses);
+    }
+  });
+}
+
+Instruction* ConvertToSampledImagePass::CreateImageExtraction(
+    Instruction* sampled_image) {
+  InstructionBuilder builder(
+      context(), sampled_image->NextNode(),
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  return builder.AddUnaryOp(
+      GetImageTypeOfSampledImage(context()->get_type_mgr(), sampled_image),
+      SpvOpImage, sampled_image->result_id());
+}
+
+uint32_t ConvertToSampledImagePass::GetSampledImageTypeForImage(
+    Instruction* image_variable) {
+  const auto* variable_type = GetVariableType(*image_variable);
+  if (variable_type == nullptr) return 0;
+  const auto* image_type = variable_type->AsImage();
+  if (image_type == nullptr) return 0;
+
+  analysis::Image image_type_for_sampled_image(*image_type);
+  analysis::SampledImage sampled_image_type(&image_type_for_sampled_image);
+  return context()->get_type_mgr()->GetTypeInstruction(&sampled_image_type);
+}
+
+Instruction* ConvertToSampledImagePass::UpdateImageUses(
+    Instruction* sampled_image_load) {
+  std::vector<Instruction*> uses_of_load;
+  FindUsesOfImage(sampled_image_load, &uses_of_load);
+  if (uses_of_load.empty()) return nullptr;
+
+  auto* extracted_image = CreateImageExtraction(sampled_image_load);
+  for (auto* user : uses_of_load) {
+    user->SetInOperand(0, {extracted_image->result_id()});
+    context()->get_def_use_mgr()->AnalyzeInstUse(user);
+  }
+  return extracted_image;
+}
+
+bool ConvertToSampledImagePass::
+    IsSamplerOfSampledImageDecoratedByDescriptorSetBinding(
+        Instruction* sampled_image_inst,
+        const DescriptorSetAndBinding& descriptor_set_binding) {
+  auto* def_use_mgr = context()->get_def_use_mgr();
+  uint32_t sampler_id = sampled_image_inst->GetSingleWordInOperand(1u);
+  auto* sampler_load = def_use_mgr->GetDef(sampler_id);
+  if (sampler_load->opcode() != SpvOpLoad) return false;
+  auto* sampler = def_use_mgr->GetDef(sampler_load->GetSingleWordInOperand(0u));
+  DescriptorSetAndBinding sampler_descriptor_set_binding;
+  return GetDescriptorSetBinding(*sampler, &sampler_descriptor_set_binding) &&
+         sampler_descriptor_set_binding == descriptor_set_binding;
+}
+
+void ConvertToSampledImagePass::UpdateSampledImageUses(
+    Instruction* image_load, Instruction* image_extraction,
+    const DescriptorSetAndBinding& image_descriptor_set_binding) {
+  std::vector<Instruction*> sampled_image_users;
+  FindUses(image_load, &sampled_image_users, SpvOpSampledImage);
+
+  auto* def_use_mgr = context()->get_def_use_mgr();
+  for (auto* sampled_image_inst : sampled_image_users) {
+    if (IsSamplerOfSampledImageDecoratedByDescriptorSetBinding(
+            sampled_image_inst, image_descriptor_set_binding)) {
+      context()->ReplaceAllUsesWith(sampled_image_inst->result_id(),
+                                    image_load->result_id());
+      def_use_mgr->AnalyzeInstUse(image_load);
+      context()->KillInst(sampled_image_inst);
+    } else {
+      if (!image_extraction)
+        image_extraction = CreateImageExtraction(image_load);
+      sampled_image_inst->SetInOperand(0, {image_extraction->result_id()});
+      def_use_mgr->AnalyzeInstUse(sampled_image_inst);
+    }
+  }
+}
+
+void ConvertToSampledImagePass::MoveInstructionNextToType(Instruction* inst,
+                                                          uint32_t type_id) {
+  auto* type_inst = context()->get_def_use_mgr()->GetDef(type_id);
+  inst->SetResultType(type_id);
+  inst->RemoveFromList();
+  inst->InsertAfter(type_inst);
+}
+
+bool ConvertToSampledImagePass::ConvertImageVariableToSampledImage(
+    Instruction* image_variable, uint32_t sampled_image_type_id) {
+  auto* sampled_image_type =
+      context()->get_type_mgr()->GetType(sampled_image_type_id);
+  if (sampled_image_type == nullptr) return false;
+  auto storage_class = GetStorageClass(*image_variable);
+  if (storage_class == SpvStorageClassMax) return false;
+  analysis::Pointer sampled_image_pointer(sampled_image_type, storage_class);
+
+  // Make sure |image_variable| is behind its type i.e., avoid the forward
+  // reference.
+  uint32_t type_id =
+      context()->get_type_mgr()->GetTypeInstruction(&sampled_image_pointer);
+  MoveInstructionNextToType(image_variable, type_id);
+  return true;
+}
+
+Pass::Status ConvertToSampledImagePass::UpdateImageVariableToSampledImage(
+    Instruction* image_variable,
+    const DescriptorSetAndBinding& descriptor_set_binding) {
+  std::vector<Instruction*> image_variable_loads;
+  FindUses(image_variable, &image_variable_loads, SpvOpLoad);
+  if (image_variable_loads.empty()) return Status::SuccessWithoutChange;
+
+  const uint32_t sampled_image_type_id =
+      GetSampledImageTypeForImage(image_variable);
+  if (!sampled_image_type_id) return Status::Failure;
+
+  for (auto* load : image_variable_loads) {
+    load->SetResultType(sampled_image_type_id);
+    auto* image_extraction = UpdateImageUses(load);
+    UpdateSampledImageUses(load, image_extraction, descriptor_set_binding);
+  }
+
+  return ConvertImageVariableToSampledImage(image_variable,
+                                            sampled_image_type_id)
+             ? Status::SuccessWithChange
+             : Status::Failure;
+}
+
+bool ConvertToSampledImagePass::DoesSampledImageReferenceImage(
+    Instruction* sampled_image_inst, Instruction* image_variable) {
+  if (sampled_image_inst->opcode() != SpvOpSampledImage) return false;
+  auto* def_use_mgr = context()->get_def_use_mgr();
+  auto* image_load = GetNonCopyObjectDef(
+      def_use_mgr, sampled_image_inst->GetSingleWordInOperand(0u));
+  if (image_load->opcode() != SpvOpLoad) return false;
+  auto* image =
+      GetNonCopyObjectDef(def_use_mgr, image_load->GetSingleWordInOperand(0u));
+  return image->opcode() == SpvOpVariable &&
+         image->result_id() == image_variable->result_id();
+}
+
+Pass::Status ConvertToSampledImagePass::CheckUsesOfSamplerVariable(
+    const Instruction* sampler_variable,
+    Instruction* image_to_be_combined_with) {
+  if (image_to_be_combined_with == nullptr) return Status::Failure;
+
+  std::vector<Instruction*> sampler_variable_loads;
+  FindUses(sampler_variable, &sampler_variable_loads, SpvOpLoad);
+  for (auto* load : sampler_variable_loads) {
+    std::vector<Instruction*> sampled_image_users;
+    FindUses(load, &sampled_image_users, SpvOpSampledImage);
+    for (auto* sampled_image_inst : sampled_image_users) {
+      if (!DoesSampledImageReferenceImage(sampled_image_inst,
+                                          image_to_be_combined_with)) {
+        return Status::Failure;
+      }
+    }
+  }
+  return Status::SuccessWithoutChange;
+}
+
+std::unique_ptr<VectorOfDescriptorSetAndBindingPairs>
+ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString(
+    const char* str) {
+  if (!str) return nullptr;
+
+  auto descriptor_set_binding_pairs =
+      MakeUnique<VectorOfDescriptorSetAndBindingPairs>();
+
+  while (std::isspace(*str)) str++;  // skip leading spaces.
+
+  // The parsing loop, break when points to the end.
+  while (*str) {
+    // Parse the descriptor set.
+    uint32_t descriptor_set = 0;
+    str = ParseNumberUntilSeparator(str, &descriptor_set);
+    if (str == nullptr) return nullptr;
+
+    // Find the ':', spaces between the descriptor set and the ':' are not
+    // allowed.
+    if (*str++ != ':') {
+      // ':' not found
+      return nullptr;
+    }
+
+    // Parse the binding.
+    uint32_t binding = 0;
+    str = ParseNumberUntilSeparator(str, &binding);
+    if (str == nullptr) return nullptr;
+
+    descriptor_set_binding_pairs->push_back({descriptor_set, binding});
+
+    // Skip trailing spaces.
+    while (std::isspace(*str)) str++;
+  }
+
+  return descriptor_set_binding_pairs;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/convert_to_sampled_image_pass.h b/source/opt/convert_to_sampled_image_pass.h
new file mode 100644
index 0000000..d3938af
--- /dev/null
+++ b/source/opt/convert_to_sampled_image_pass.h
@@ -0,0 +1,207 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_CONVERT_TO_SAMPLED_IMAGE_PASS_H_
+#define SOURCE_OPT_CONVERT_TO_SAMPLED_IMAGE_PASS_H_
+
+#include <memory>
+#include <unordered_set>
+#include <utility>
+
+#include "source/opt/pass.h"
+#include "source/opt/types.h"
+
+namespace spvtools {
+namespace opt {
+
+// A struct for a pair of descriptor set and binding.
+struct DescriptorSetAndBinding {
+  uint32_t descriptor_set;
+  uint32_t binding;
+
+  bool operator==(const DescriptorSetAndBinding& descriptor_set_binding) const {
+    return descriptor_set_binding.descriptor_set == descriptor_set &&
+           descriptor_set_binding.binding == binding;
+  }
+};
+
+// See optimizer.hpp for documentation.
+class ConvertToSampledImagePass : public Pass {
+ public:
+  // Hashing functor for the pair of descriptor set and binding.
+  struct DescriptorSetAndBindingHash {
+    size_t operator()(
+        const DescriptorSetAndBinding& descriptor_set_binding) const {
+      return std::hash<uint32_t>()(descriptor_set_binding.descriptor_set) ^
+             std::hash<uint32_t>()(descriptor_set_binding.binding);
+    }
+  };
+
+  using SetOfDescriptorSetAndBindingPairs =
+      std::unordered_set<DescriptorSetAndBinding, DescriptorSetAndBindingHash>;
+  using DescriptorSetBindingToInstruction =
+      std::unordered_map<DescriptorSetAndBinding, Instruction*,
+                         DescriptorSetAndBindingHash>;
+
+  explicit ConvertToSampledImagePass(
+      const std::vector<DescriptorSetAndBinding>& descriptor_set_binding_pairs)
+      : descriptor_set_binding_pairs_(descriptor_set_binding_pairs.begin(),
+                                      descriptor_set_binding_pairs.end()) {}
+
+  const char* name() const override { return "convert-to-sampled-image"; }
+  Status Process() override;
+
+  // Parses the given null-terminated C string to get a vector of descriptor set
+  // and binding pairs. Returns a unique pointer to the vector of descriptor set
+  // and binding pairs built from the given |str| on success. Returns a nullptr
+  // if the given string is not valid for building the vector of pairs.
+  // A valid string for building the vector of pairs should follow the rule
+  // below:
+  //
+  //  "<descriptor set>:<binding> <descriptor set>:<binding> ..."
+  //  Example:
+  //    "3:5 2:1 0:4"
+  //
+  //  Entries are separated with blank spaces (i.e.:' ', '\n', '\r', '\t',
+  //  '\f', '\v'). Each entry corresponds to a descriptor set and binding pair.
+  //  Multiple spaces between, before or after entries are allowed. However,
+  //  spaces are not allowed within a descriptor set or binding.
+  //
+  //  In each entry, the descriptor set and binding are separated by ':'.
+  //  Missing ':' in any entry is invalid. And it is invalid to have blank
+  //  spaces in between the descriptor set and ':' or ':' and the binding.
+  //
+  //  <descriptor set>: the descriptor set.
+  //    The text must represent a valid uint32_t number.
+  //
+  //  <binding>: the binding.
+  //    The text must represent a valid uint32_t number.
+  static std::unique_ptr<std::vector<DescriptorSetAndBinding>>
+  ParseDescriptorSetBindingPairsString(const char* str);
+
+ private:
+  // Collects resources to convert to sampled image and saves them in
+  // |descriptor_set_binding_pair_to_sampler| if the resource is a sampler and
+  // saves them in |descriptor_set_binding_pair_to_image| if the resource is an
+  // image. Returns false if two samplers or two images have the same descriptor
+  // set and binding. Otherwise, returns true.
+  bool CollectResourcesToConvert(
+      DescriptorSetBindingToInstruction* descriptor_set_binding_pair_to_sampler,
+      DescriptorSetBindingToInstruction* descriptor_set_binding_pair_to_image)
+      const;
+
+  // Finds an OpDecorate with DescriptorSet decorating |inst| and another
+  // OpDecorate with Binding decorating |inst|. Stores the descriptor set and
+  // binding in |descriptor_set_binding|. Returns whether it successfully finds
+  // the descriptor set and binding or not.
+  bool GetDescriptorSetBinding(
+      const Instruction& inst,
+      DescriptorSetAndBinding* descriptor_set_binding) const;
+
+  // Returns whether |descriptor_set_binding| is a pair of a descriptor set
+  // and a binding that we have to convert resources with it to a sampled image
+  // or not.
+  bool ShouldResourceBeConverted(
+      const DescriptorSetAndBinding& descriptor_set_binding) const;
+
+  // Returns the pointee type of the type of variable |variable|. If |variable|
+  // is not an OpVariable instruction, just returns nullptr.
+  const analysis::Type* GetVariableType(const Instruction& variable) const;
+
+  // Returns the storage class of |variable|.
+  SpvStorageClass GetStorageClass(const Instruction& variable) const;
+
+  // Finds |inst|'s users whose opcode is |user_opcode| or users of OpCopyObject
+  // instructions of |inst| whose opcode is |user_opcode| and puts them in
+  // |uses|.
+  void FindUses(const Instruction* inst, std::vector<Instruction*>* uses,
+                uint32_t user_opcode) const;
+
+  // Finds OpImage* instructions using |image| or OpCopyObject instructions that
+  // copy |image| and puts them in |uses|.
+  void FindUsesOfImage(const Instruction* image,
+                       std::vector<Instruction*>* uses) const;
+
+  // Creates an OpImage instruction that extracts the image from the sampled
+  // image |sampled_image|.
+  Instruction* CreateImageExtraction(Instruction* sampled_image);
+
+  // Converts |image_variable| whose type is an image pointer to sampled image
+  // type. Updates users of |image_variable| accordingly. If some instructions
+  // e.g., OpImageRead use |image_variable| as an Image operand, creates an
+  // image extracted from the sampled image using OpImage and replace the Image
+  // operands of the users with the extracted image. If some OpSampledImage
+  // instructions use |image_variable| and sampler whose descriptor set and
+  // binding are the same with |image_variable|, just combines |image_variable|
+  // and the sampler to a sampled image.
+  Pass::Status UpdateImageVariableToSampledImage(
+      Instruction* image_variable,
+      const DescriptorSetAndBinding& descriptor_set_binding);
+
+  // Returns the id of type sampled image type whose image type is the one of
+  // |image_variable|.
+  uint32_t GetSampledImageTypeForImage(Instruction* image_variable);
+
+  // Moves |inst| next to the OpType* instruction with |type_id|.
+  void MoveInstructionNextToType(Instruction* inst, uint32_t type_id);
+
+  // Converts |image_variable| whose type is an image pointer to sampled image
+  // with the type id |sampled_image_type_id|. Returns whether it successfully
+  // converts the type of |image_variable| or not.
+  bool ConvertImageVariableToSampledImage(Instruction* image_variable,
+                                          uint32_t sampled_image_type_id);
+
+  // Replaces |sampled_image_load| instruction used by OpImage* with the image
+  // extracted from |sampled_image_load|. Returns the extracted image or nullptr
+  // if it does not have uses.
+  Instruction* UpdateImageUses(Instruction* sampled_image_load);
+
+  // Returns true if the sampler of |sampled_image_inst| is decorated by a
+  // descriptor set and a binding |descriptor_set_binding|.
+  bool IsSamplerOfSampledImageDecoratedByDescriptorSetBinding(
+      Instruction* sampled_image_inst,
+      const DescriptorSetAndBinding& descriptor_set_binding);
+
+  // Replaces OpSampledImage instructions using |image_load| with |image_load|
+  // if the sampler of the OpSampledImage instruction has descriptor set and
+  // binding |image_descriptor_set_binding|. Otherwise, replaces |image_load|
+  // with |image_extraction|.
+  void UpdateSampledImageUses(
+      Instruction* image_load, Instruction* image_extraction,
+      const DescriptorSetAndBinding& image_descriptor_set_binding);
+
+  // Checks the uses of |sampler_variable|. When a sampler is used by
+  // OpSampledImage instruction, the corresponding image must be
+  // |image_to_be_combined_with| that should be already converted to a sampled
+  // image by UpdateImageVariableToSampledImage() method.
+  Pass::Status CheckUsesOfSamplerVariable(
+      const Instruction* sampler_variable,
+      Instruction* image_to_be_combined_with);
+
+  // Returns true if Image operand of |sampled_image_inst| is the image of
+  // |image_variable|.
+  bool DoesSampledImageReferenceImage(Instruction* sampled_image_inst,
+                                      Instruction* image_variable);
+
+  // A set of pairs of descriptor set and binding. If an image and/or a sampler
+  // have a pair of descriptor set and binding that is an element of
+  // |descriptor_set_binding_pairs_|, they/it will be converted to a sampled
+  // image by this pass.
+  const SetOfDescriptorSetAndBindingPairs descriptor_set_binding_pairs_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_CONVERT_TO_SAMPLED_IMAGE_PASS_H_
diff --git a/source/opt/copy_prop_arrays.cpp b/source/opt/copy_prop_arrays.cpp
index 67a97b6..62ed5e7 100644
--- a/source/opt/copy_prop_arrays.cpp
+++ b/source/opt/copy_prop_arrays.cpp
@@ -29,10 +29,10 @@
 const uint32_t kTypePointerStorageClassInIdx = 0;
 const uint32_t kTypePointerPointeeInIdx = 1;
 
-bool IsOpenCL100DebugDeclareOrValue(Instruction* di) {
-  auto dbg_opcode = di->GetOpenCL100DebugOpcode();
-  return dbg_opcode == OpenCLDebugInfo100DebugDeclare ||
-         dbg_opcode == OpenCLDebugInfo100DebugValue;
+bool IsDebugDeclareOrValue(Instruction* di) {
+  auto dbg_opcode = di->GetCommonDebugOpcode();
+  return dbg_opcode == CommonDebugInfoDebugDeclare ||
+         dbg_opcode == CommonDebugInfoDebugValue;
 }
 
 }  // namespace
@@ -40,6 +40,10 @@
 Pass::Status CopyPropagateArrays::Process() {
   bool modified = false;
   for (Function& function : *get_module()) {
+    if (function.IsDeclaration()) {
+      continue;
+    }
+
     BasicBlock* entry_bb = &*function.begin();
 
     for (auto var_inst = entry_bb->begin(); var_inst->opcode() == SpvOpVariable;
@@ -194,7 +198,7 @@
           return ptr_inst->opcode() == SpvOpVariable &&
                  store_inst->GetSingleWordInOperand(kStorePointerInOperand) ==
                      ptr_inst->result_id();
-        } else if (IsOpenCL100DebugDeclareOrValue(use)) {
+        } else if (IsDebugDeclareOrValue(use)) {
           return true;
         }
         // Some other instruction.  Be conservative.
@@ -500,7 +504,7 @@
                                                        const_mgr,
                                                        type](Instruction* use,
                                                              uint32_t) {
-    if (IsOpenCL100DebugDeclareOrValue(use)) return true;
+    if (IsDebugDeclareOrValue(use)) return true;
 
     switch (use->opcode()) {
       case SpvOpLoad: {
@@ -592,9 +596,9 @@
     Instruction* use = pair.first;
     uint32_t index = pair.second;
 
-    if (use->IsOpenCL100DebugInstr()) {
-      switch (use->GetOpenCL100DebugOpcode()) {
-        case OpenCLDebugInfo100DebugDeclare: {
+    if (use->IsCommonDebugInstr()) {
+      switch (use->GetCommonDebugOpcode()) {
+        case CommonDebugInfoDebugDeclare: {
           if (new_ptr_inst->opcode() == SpvOpVariable ||
               new_ptr_inst->opcode() == SpvOpFunctionParameter) {
             context()->ForgetUses(use);
@@ -608,9 +612,8 @@
             context()->ForgetUses(use);
 
             // Change DebugDeclare to DebugValue.
-            use->SetOperand(
-                index - 2,
-                {static_cast<uint32_t>(OpenCLDebugInfo100DebugValue)});
+            use->SetOperand(index - 2,
+                            {static_cast<uint32_t>(CommonDebugInfoDebugValue)});
             use->SetOperand(index, {new_ptr_inst->result_id()});
 
             // Add Deref operation.
@@ -625,7 +628,7 @@
           }
           break;
         }
-        case OpenCLDebugInfo100DebugValue:
+        case CommonDebugInfoDebugValue:
           context()->ForgetUses(use);
           use->SetOperand(index, {new_ptr_inst->result_id()});
           context()->AnalyzeUses(use);
diff --git a/source/opt/dataflow.cpp b/source/opt/dataflow.cpp
new file mode 100644
index 0000000..c91fad0
--- /dev/null
+++ b/source/opt/dataflow.cpp
@@ -0,0 +1,91 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/dataflow.h"
+
+#include <algorithm>
+#include <cstdint>
+
+namespace spvtools {
+namespace opt {
+
+bool DataFlowAnalysis::Enqueue(Instruction* inst) {
+  bool& is_enqueued = on_worklist_[inst];
+  if (is_enqueued) return false;
+  is_enqueued = true;
+  worklist_.push(inst);
+  return true;
+}
+
+DataFlowAnalysis::VisitResult DataFlowAnalysis::RunOnce(
+    Function* function, bool is_first_iteration) {
+  InitializeWorklist(function, is_first_iteration);
+  VisitResult ret = VisitResult::kResultFixed;
+  while (!worklist_.empty()) {
+    Instruction* top = worklist_.front();
+    worklist_.pop();
+    on_worklist_[top] = false;
+    VisitResult result = Visit(top);
+    if (result == VisitResult::kResultChanged) {
+      EnqueueSuccessors(top);
+      ret = VisitResult::kResultChanged;
+    }
+  }
+  return ret;
+}
+
+void DataFlowAnalysis::Run(Function* function) {
+  VisitResult result = RunOnce(function, true);
+  while (result == VisitResult::kResultChanged) {
+    result = RunOnce(function, false);
+  }
+}
+
+void ForwardDataFlowAnalysis::InitializeWorklist(Function* function,
+                                                 bool /*is_first_iteration*/) {
+  context().cfg()->ForEachBlockInReversePostOrder(
+      function->entry().get(), [this](BasicBlock* bb) {
+        if (label_position_ == LabelPosition::kLabelsOnly) {
+          Enqueue(bb->GetLabelInst());
+          return;
+        }
+        if (label_position_ == LabelPosition::kLabelsAtBeginning) {
+          Enqueue(bb->GetLabelInst());
+        }
+        for (Instruction& inst : *bb) {
+          Enqueue(&inst);
+        }
+        if (label_position_ == LabelPosition::kLabelsAtEnd) {
+          Enqueue(bb->GetLabelInst());
+        }
+      });
+}
+
+void ForwardDataFlowAnalysis::EnqueueUsers(Instruction* inst) {
+  context().get_def_use_mgr()->ForEachUser(
+      inst, [this](Instruction* user) { Enqueue(user); });
+}
+
+void ForwardDataFlowAnalysis::EnqueueBlockSuccessors(Instruction* inst) {
+  if (inst->opcode() != SpvOpLabel) return;
+  context()
+      .cfg()
+      ->block(inst->result_id())
+      ->ForEachSuccessorLabel([this](uint32_t* label) {
+        Enqueue(context().cfg()->block(*label)->GetLabelInst());
+      });
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/dataflow.h b/source/opt/dataflow.h
new file mode 100644
index 0000000..be07415
--- /dev/null
+++ b/source/opt/dataflow.h
@@ -0,0 +1,148 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_DATAFLOW_H_
+#define SOURCE_OPT_DATAFLOW_H_
+
+#include <queue>
+#include <unordered_map>
+#include <vector>
+
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Generic data-flow analysis.
+// Maintains a worklist of instructions to process and processes them in a
+// specified order. See also ForwardDataFlowAnalysis, which is specialized for
+// forward data-flow analysis.
+class DataFlowAnalysis {
+ public:
+  // The result of a |Visit| operation on an instruction.
+  // This is used to determine when analysis has reached a fixpoint.
+  enum class VisitResult {
+    // The analysis result for this instruction has changed.
+    // This means that any instructions that depend on it (its successors) must
+    // be recomputed.
+    kResultChanged,
+    // The analysis result for this instruction has not changed.
+    // When all visit operations return |kResultFixed|, the analysis has reached
+    // a fixpoint (converged).
+    kResultFixed,
+  };
+
+  virtual ~DataFlowAnalysis() {}
+
+  // Run this analysis on a given function.
+  // For analyses which work interprocedurally, |function| may be ignored.
+  void Run(Function* function);
+
+ protected:
+  DataFlowAnalysis(IRContext& context) : context_(context) {}
+
+  // Initialize the worklist for a given function.
+  // |is_first_iteration| is true on the first call to |Run| and false
+  // afterwards. All subsequent runs are only necessary to check if the analysis
+  // has converged; if |EnqueueSuccessors| is complete, |InitializeWorklist|
+  // should do nothing after the first iteration.
+  virtual void InitializeWorklist(Function* function,
+                                  bool is_first_iteration) = 0;
+
+  // Enqueues the successors (instructions which use the analysis result) of
+  // |inst|. This is not required to be complete, but convergence is faster when
+  // it is. This is called whenever |Visit| returns |kResultChanged|.
+  virtual void EnqueueSuccessors(Instruction* inst) = 0;
+
+  // Visits the given instruction, recomputing the analysis result. This is
+  // called once per instruction queued in |InitializeWorklist| and afterward
+  // when a predecessor is changed, through |EnqueueSuccessors|.
+  virtual VisitResult Visit(Instruction* inst) = 0;
+
+  // Enqueues the given instruction to be visited. Ignored if already in the
+  // worklist.
+  bool Enqueue(Instruction* inst);
+
+  IRContext& context() { return context_; }
+
+ private:
+  // Runs one pass, calling |InitializeWorklist| and then iterating through the
+  // worklist until all fixed.
+  VisitResult RunOnce(Function* function, bool is_first_iteration);
+
+  IRContext& context_;
+  std::unordered_map<Instruction*, bool> on_worklist_;
+  // The worklist, which contains the list of instructions to be visited.
+  //
+  // The choice of data structure was influenced by the data in "Iterative
+  // Data-flow Analysis, Revisited" (Cooper et al, 2002).
+  // https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.125.1549&rep=rep1&type=pdf
+  // The paper shows that the overall performance benefit of a priority queue
+  // over a regular queue or stack is relatively small (or negative).
+  //
+  // A queue has the advantage that nodes are visited in the same order they are
+  // enqueued, which relieves the analysis from inserting nodes "backwards", for
+  // example in worklist initialization. Also, as the paper claims that sorting
+  // successors does not improve runtime, we can use a single queue which is
+  // modified during iteration.
+  std::queue<Instruction*> worklist_;
+};
+
+// A generic data flow analysis, specialized for forward analysis.
+class ForwardDataFlowAnalysis : public DataFlowAnalysis {
+ public:
+  // Indicates where labels should be in the worklist RPO ordering.
+  enum class LabelPosition {
+    // Labels should be placed at the beginning of their blocks.
+    kLabelsAtBeginning,
+    // Labels should be placed at the end of their blocks.
+    kLabelsAtEnd,
+    // Labels should not be in the worklist.
+    kNoLabels,
+    // Only labels should be placed in the worklist.
+    kLabelsOnly,
+  };
+
+  ForwardDataFlowAnalysis(IRContext& context, LabelPosition label_position)
+      : DataFlowAnalysis(context), label_position_(label_position) {}
+
+ protected:
+  // Initializes the worklist in reverse postorder, regardless of
+  // |is_first_iteration|. Labels are placed according to the label position
+  // specified in the constructor.
+  void InitializeWorklist(Function* function, bool is_first_iteration) override;
+
+  // Enqueues the users and block successors of the given instruction.
+  // See |EnqueueUsers| and |EnqueueBlockSuccessors|.
+  void EnqueueSuccessors(Instruction* inst) override {
+    EnqueueUsers(inst);
+    EnqueueBlockSuccessors(inst);
+  }
+
+  // Enqueues the users of the given instruction.
+  void EnqueueUsers(Instruction* inst);
+
+  // Enqueues the labels of the successors of the block corresponding to the
+  // given label instruction. Does nothing for other instructions.
+  void EnqueueBlockSuccessors(Instruction* inst);
+
+ private:
+  LabelPosition label_position_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_DATAFLOW_H_
diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp
index 0054f57..356dbcb 100644
--- a/source/opt/dead_branch_elim_pass.cpp
+++ b/source/opt/dead_branch_elim_pass.cpp
@@ -346,6 +346,7 @@
           if (operands.size() == 4) {
             // First input data operands is at index 2.
             uint32_t replId = operands[2u].words[0];
+            context()->KillNamesAndDecorates(inst->result_id());
             context()->ReplaceAllUsesWith(inst->result_id(), replId);
             iter = context()->KillInst(&*inst);
           } else {
@@ -419,6 +420,10 @@
 }
 
 bool DeadBranchElimPass::EliminateDeadBranches(Function* func) {
+  if (func->IsDeclaration()) {
+    return false;
+  }
+
   bool modified = false;
   std::unordered_set<BasicBlock*> live_blocks;
   modified |= MarkLiveBlocks(func, &live_blocks);
diff --git a/source/opt/dead_insert_elim_pass.cpp b/source/opt/dead_insert_elim_pass.cpp
index 46f4f12..d877f0f 100644
--- a/source/opt/dead_insert_elim_pass.cpp
+++ b/source/opt/dead_insert_elim_pass.cpp
@@ -196,7 +196,7 @@
       }
       const uint32_t id = ii->result_id();
       get_def_use_mgr()->ForEachUser(id, [&ii, this](Instruction* user) {
-        if (user->IsOpenCL100DebugInstr()) return;
+        if (user->IsCommonDebugInstr()) return;
         switch (user->opcode()) {
           case SpvOpCompositeInsert:
           case SpvOpPhi:
@@ -256,7 +256,7 @@
   ProcessFunction pfn = [this](Function* fp) {
     return EliminateDeadInserts(fp);
   };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
+  bool modified = context()->ProcessReachableCallTree(pfn);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/debug_info_manager.cpp b/source/opt/debug_info_manager.cpp
index 11b5131..060e0d9 100644
--- a/source/opt/debug_info_manager.cpp
+++ b/source/opt/debug_info_manager.cpp
@@ -18,12 +18,15 @@
 
 #include "source/opt/ir_context.h"
 
-// Constants for OpenCL.DebugInfo.100 extension instructions.
+// Constants for OpenCL.DebugInfo.100 & NonSemantic.Shader.DebugInfo.100
+// extension instructions.
 
 static const uint32_t kOpLineOperandLineIndex = 1;
 static const uint32_t kLineOperandIndexDebugFunction = 7;
 static const uint32_t kLineOperandIndexDebugLexicalBlock = 5;
 static const uint32_t kDebugFunctionOperandFunctionIndex = 13;
+static const uint32_t kDebugFunctionDefinitionOperandDebugFunctionIndex = 4;
+static const uint32_t kDebugFunctionDefinitionOperandOpFunctionIndex = 5;
 static const uint32_t kDebugFunctionOperandParentIndex = 9;
 static const uint32_t kDebugTypeCompositeOperandParentIndex = 9;
 static const uint32_t kDebugLexicalBlockOperandParentIndex = 7;
@@ -46,8 +49,8 @@
 
 void SetInlinedOperand(Instruction* dbg_inlined_at, uint32_t inlined_operand) {
   assert(dbg_inlined_at);
-  assert(dbg_inlined_at->GetOpenCL100DebugOpcode() ==
-         OpenCLDebugInfo100DebugInlinedAt);
+  assert(dbg_inlined_at->GetCommonDebugOpcode() ==
+         CommonDebugInfoDebugInlinedAt);
   if (dbg_inlined_at->NumOperands() <= kDebugInlinedAtOperandInlinedIndex) {
     dbg_inlined_at->AddOperand(
         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {inlined_operand}});
@@ -59,8 +62,8 @@
 
 uint32_t GetInlinedOperand(Instruction* dbg_inlined_at) {
   assert(dbg_inlined_at);
-  assert(dbg_inlined_at->GetOpenCL100DebugOpcode() ==
-         OpenCLDebugInfo100DebugInlinedAt);
+  assert(dbg_inlined_at->GetCommonDebugOpcode() ==
+         CommonDebugInfoDebugInlinedAt);
   if (dbg_inlined_at->NumOperands() <= kDebugInlinedAtOperandInlinedIndex)
     return kNoInlinedAt;
   return dbg_inlined_at->GetSingleWordOperand(
@@ -68,8 +71,7 @@
 }
 
 bool IsEmptyDebugExpression(Instruction* instr) {
-  return instr->GetOpenCL100DebugOpcode() ==
-             OpenCLDebugInfo100DebugExpression &&
+  return (instr->GetCommonDebugOpcode() == CommonDebugInfoDebugExpression) &&
          instr->NumOperands() == kDebugExpressOperandOperationIndex;
 }
 
@@ -79,43 +81,63 @@
   AnalyzeDebugInsts(*c->module());
 }
 
+uint32_t DebugInfoManager::GetDbgSetImportId() {
+  uint32_t setId =
+      context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo();
+  if (setId == 0) {
+    setId =
+        context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo();
+  }
+  return setId;
+}
+
 Instruction* DebugInfoManager::GetDbgInst(uint32_t id) {
   auto dbg_inst_it = id_to_dbg_inst_.find(id);
   return dbg_inst_it == id_to_dbg_inst_.end() ? nullptr : dbg_inst_it->second;
 }
 
 void DebugInfoManager::RegisterDbgInst(Instruction* inst) {
-  assert(
-      inst->NumInOperands() != 0 &&
-      context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo() ==
-          inst->GetInOperand(0).words[0] &&
-      "Given instruction is not a debug instruction");
+  assert(inst->NumInOperands() != 0 &&
+         (GetDbgSetImportId() == inst->GetInOperand(0).words[0]) &&
+         "Given instruction is not a debug instruction");
   id_to_dbg_inst_[inst->result_id()] = inst;
 }
 
 void DebugInfoManager::RegisterDbgFunction(Instruction* inst) {
-  assert(inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction &&
-         "inst is not a DebugFunction");
-  auto fn_id = inst->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex);
-  // Do not register function that has been optimized away
-  auto fn_inst = GetDbgInst(fn_id);
-  if (fn_inst != nullptr) {
-    assert(GetDbgInst(fn_id)->GetOpenCL100DebugOpcode() ==
-           OpenCLDebugInfo100DebugInfoNone);
-    return;
+  if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) {
+    auto fn_id = inst->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex);
+    // Do not register function that has been optimized away.
+    auto fn_inst = GetDbgInst(fn_id);
+    if (fn_inst != nullptr) {
+      assert(GetDbgInst(fn_id)->GetOpenCL100DebugOpcode() ==
+             OpenCLDebugInfo100DebugInfoNone);
+      return;
+    }
+    assert(
+        fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() &&
+        "Register DebugFunction for a function that already has DebugFunction");
+    fn_id_to_dbg_fn_[fn_id] = inst;
+  } else if (inst->GetShader100DebugOpcode() ==
+             NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
+    auto fn_id = inst->GetSingleWordOperand(
+        kDebugFunctionDefinitionOperandOpFunctionIndex);
+    auto fn_inst = GetDbgInst(inst->GetSingleWordOperand(
+        kDebugFunctionDefinitionOperandDebugFunctionIndex));
+    assert(fn_inst && fn_inst->GetShader100DebugOpcode() ==
+                          NonSemanticShaderDebugInfo100DebugFunction);
+    assert(fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() &&
+           "Register DebugFunctionDefinition for a function that already has "
+           "DebugFunctionDefinition");
+    fn_id_to_dbg_fn_[fn_id] = fn_inst;
+  } else {
+    assert(false && "inst is not a DebugFunction");
   }
-  assert(
-      fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() &&
-      "Register DebugFunction for a function that already has DebugFunction");
-  fn_id_to_dbg_fn_[fn_id] = inst;
 }
 
 void DebugInfoManager::RegisterDbgDeclare(uint32_t var_id,
                                           Instruction* dbg_declare) {
-  assert(dbg_declare->GetOpenCL100DebugOpcode() ==
-             OpenCLDebugInfo100DebugDeclare ||
-         dbg_declare->GetOpenCL100DebugOpcode() ==
-             OpenCLDebugInfo100DebugValue);
+  assert(dbg_declare->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
+         dbg_declare->GetCommonDebugOpcode() == CommonDebugInfoDebugValue);
   auto dbg_decl_itr = var_id_to_dbg_decl_.find(var_id);
   if (dbg_decl_itr == var_id_to_dbg_decl_.end()) {
     var_id_to_dbg_decl_[var_id] = {dbg_declare};
@@ -124,29 +146,57 @@
   }
 }
 
+// Create new constant directly into global value area, bypassing the
+// Constant manager. This is used when the DefUse or Constant managers
+// are invalid and cannot be regenerated due to the module being in an
+// inconsistant state e.g. in the middle of significant modification
+// such as inlining. Invalidate Constant and DefUse managers if used.
+uint32_t AddNewConstInGlobals(IRContext* context, uint32_t const_value) {
+  uint32_t id = context->TakeNextId();
+  std::unique_ptr<Instruction> new_const(new Instruction(
+      context, SpvOpConstant, context->get_type_mgr()->GetUIntTypeId(), id,
+      {
+          {spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER,
+           {const_value}},
+      }));
+  context->module()->AddGlobalValue(std::move(new_const));
+  context->InvalidateAnalyses(IRContext::kAnalysisConstants);
+  context->InvalidateAnalyses(IRContext::kAnalysisDefUse);
+  return id;
+}
+
 uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line,
                                                 const DebugScope& scope) {
-  if (context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo() ==
-      0)
-    return kNoInlinedAt;
+  uint32_t setId = GetDbgSetImportId();
+
+  if (setId == 0) return kNoInlinedAt;
+
+  spv_operand_type_t line_number_type =
+      spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER;
+
+  // In NonSemantic.Shader.DebugInfo.100, all constants are IDs of OpConstant,
+  // not literals.
+  if (setId ==
+      context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo())
+    line_number_type = spv_operand_type_t::SPV_OPERAND_TYPE_ID;
 
   uint32_t line_number = 0;
   if (line == nullptr) {
     auto* lexical_scope_inst = GetDbgInst(scope.GetLexicalScope());
     if (lexical_scope_inst == nullptr) return kNoInlinedAt;
-    OpenCLDebugInfo100Instructions debug_opcode =
-        lexical_scope_inst->GetOpenCL100DebugOpcode();
+    CommonDebugInfoInstructions debug_opcode =
+        lexical_scope_inst->GetCommonDebugOpcode();
     switch (debug_opcode) {
-      case OpenCLDebugInfo100DebugFunction:
+      case CommonDebugInfoDebugFunction:
         line_number = lexical_scope_inst->GetSingleWordOperand(
             kLineOperandIndexDebugFunction);
         break;
-      case OpenCLDebugInfo100DebugLexicalBlock:
+      case CommonDebugInfoDebugLexicalBlock:
         line_number = lexical_scope_inst->GetSingleWordOperand(
             kLineOperandIndexDebugLexicalBlock);
         break;
-      case OpenCLDebugInfo100DebugTypeComposite:
-      case OpenCLDebugInfo100DebugCompilationUnit:
+      case CommonDebugInfoDebugTypeComposite:
+      case CommonDebugInfoDebugCompilationUnit:
         assert(false &&
                "DebugTypeComposite and DebugCompilationUnit are lexical "
                "scopes, but we inline functions into a function or a block "
@@ -161,6 +211,21 @@
     }
   } else {
     line_number = line->GetSingleWordOperand(kOpLineOperandLineIndex);
+
+    // If we need the line number as an ID, generate that constant now.
+    // If Constant or DefUse managers are invalid, generate constant
+    // directly into the global value section of the module; do not
+    // use Constant manager which may attempt to invoke building of the
+    // DefUse manager which cannot be done during inlining. The extra
+    // constants that may be generated here is likely not significant
+    // and will likely be cleaned up in later passes.
+    if (line_number_type == spv_operand_type_t::SPV_OPERAND_TYPE_ID) {
+      if (!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse) ||
+          !context()->AreAnalysesValid(IRContext::Analysis::kAnalysisConstants))
+        line_number = AddNewConstInGlobals(context(), line_number);
+      else
+        line_number = context()->get_constant_mgr()->GetUIntConst(line_number);
+    }
   }
 
   uint32_t result_id = context()->TakeNextId();
@@ -168,13 +233,10 @@
       context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
       result_id,
       {
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-           {context()
-                ->get_feature_mgr()
-                ->GetExtInstImportId_OpenCL100DebugInfo()}},
+          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {setId}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
-           {static_cast<uint32_t>(OpenCLDebugInfo100DebugInlinedAt)}},
-          {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {line_number}},
+           {static_cast<uint32_t>(CommonDebugInfoDebugInlinedAt)}},
+          {line_number_type, {line_number}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {scope.GetLexicalScope()}},
       }));
   // |scope| already has DebugInlinedAt. We put the existing DebugInlinedAt
@@ -257,19 +319,34 @@
   if (deref_operation_ != nullptr) return deref_operation_;
 
   uint32_t result_id = context()->TakeNextId();
-  std::unique_ptr<Instruction> deref_operation(new Instruction(
-      context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
-      result_id,
-      {
-          {SPV_OPERAND_TYPE_ID,
-           {context()
-                ->get_feature_mgr()
-                ->GetExtInstImportId_OpenCL100DebugInfo()}},
-          {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
-           {static_cast<uint32_t>(OpenCLDebugInfo100DebugOperation)}},
-          {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION,
-           {static_cast<uint32_t>(OpenCLDebugInfo100Deref)}},
-      }));
+  std::unique_ptr<Instruction> deref_operation;
+
+  if (context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) {
+    deref_operation = std::unique_ptr<Instruction>(new Instruction(
+        context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
+        result_id,
+        {
+            {SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
+            {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
+             {static_cast<uint32_t>(OpenCLDebugInfo100DebugOperation)}},
+            {SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_OPERATION,
+             {static_cast<uint32_t>(OpenCLDebugInfo100Deref)}},
+        }));
+  } else {
+    uint32_t deref_id = context()->get_constant_mgr()->GetUIntConst(
+        NonSemanticShaderDebugInfo100Deref);
+
+    deref_operation = std::unique_ptr<Instruction>(
+        new Instruction(context(), SpvOpExtInst,
+                        context()->get_type_mgr()->GetVoidTypeId(), result_id,
+                        {
+                            {SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
+                            {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
+                             {static_cast<uint32_t>(
+                                 NonSemanticShaderDebugInfo100DebugOperation)}},
+                            {SPV_OPERAND_TYPE_ID, {deref_id}},
+                        }));
+  }
 
   // Add to the front of |ext_inst_debuginfo_|.
   deref_operation_ =
@@ -283,8 +360,7 @@
 }
 
 Instruction* DebugInfoManager::DerefDebugExpression(Instruction* dbg_expr) {
-  assert(dbg_expr->GetOpenCL100DebugOpcode() ==
-         OpenCLDebugInfo100DebugExpression);
+  assert(dbg_expr->GetCommonDebugOpcode() == CommonDebugInfoDebugExpression);
   std::unique_ptr<Instruction> deref_expr(dbg_expr->Clone(context()));
   deref_expr->SetResultId(context()->TakeNextId());
   deref_expr->InsertOperand(
@@ -306,12 +382,9 @@
       context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
       result_id,
       {
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-           {context()
-                ->get_feature_mgr()
-                ->GetExtInstImportId_OpenCL100DebugInfo()}},
+          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
-           {static_cast<uint32_t>(OpenCLDebugInfo100DebugInfoNone)}},
+           {static_cast<uint32_t>(CommonDebugInfoDebugInfoNone)}},
       }));
 
   // Add to the front of |ext_inst_debuginfo_|.
@@ -333,12 +406,9 @@
       context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
       result_id,
       {
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-           {context()
-                ->get_feature_mgr()
-                ->GetExtInstImportId_OpenCL100DebugInfo()}},
+          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
-           {static_cast<uint32_t>(OpenCLDebugInfo100DebugExpression)}},
+           {static_cast<uint32_t>(CommonDebugInfoDebugExpression)}},
       }));
 
   // Add to the front of |ext_inst_debuginfo_|.
@@ -355,8 +425,7 @@
 Instruction* DebugInfoManager::GetDebugInlinedAt(uint32_t dbg_inlined_at_id) {
   auto* inlined_at = GetDbgInst(dbg_inlined_at_id);
   if (inlined_at == nullptr) return nullptr;
-  if (inlined_at->GetOpenCL100DebugOpcode() !=
-      OpenCLDebugInfo100DebugInlinedAt) {
+  if (inlined_at->GetCommonDebugOpcode() != CommonDebugInfoDebugInlinedAt) {
     return nullptr;
   }
   return inlined_at;
@@ -403,23 +472,23 @@
 uint32_t DebugInfoManager::GetParentScope(uint32_t child_scope) {
   auto dbg_scope_itr = id_to_dbg_inst_.find(child_scope);
   assert(dbg_scope_itr != id_to_dbg_inst_.end());
-  OpenCLDebugInfo100Instructions debug_opcode =
-      dbg_scope_itr->second->GetOpenCL100DebugOpcode();
+  CommonDebugInfoInstructions debug_opcode =
+      dbg_scope_itr->second->GetCommonDebugOpcode();
   uint32_t parent_scope = kNoDebugScope;
   switch (debug_opcode) {
-    case OpenCLDebugInfo100DebugFunction:
+    case CommonDebugInfoDebugFunction:
       parent_scope = dbg_scope_itr->second->GetSingleWordOperand(
           kDebugFunctionOperandParentIndex);
       break;
-    case OpenCLDebugInfo100DebugLexicalBlock:
+    case CommonDebugInfoDebugLexicalBlock:
       parent_scope = dbg_scope_itr->second->GetSingleWordOperand(
           kDebugLexicalBlockOperandParentIndex);
       break;
-    case OpenCLDebugInfo100DebugTypeComposite:
+    case CommonDebugInfoDebugTypeComposite:
       parent_scope = dbg_scope_itr->second->GetSingleWordOperand(
           kDebugTypeCompositeOperandParentIndex);
       break;
-    case OpenCLDebugInfo100DebugCompilationUnit:
+    case CommonDebugInfoDebugCompilationUnit:
       // DebugCompilationUnit does not have a parent scope.
       break;
     default:
@@ -500,23 +569,24 @@
            insert_before->opcode() == SpvOpVariable) {
       insert_before = insert_before->NextNode();
     }
-    modified |= AddDebugValueForDecl(dbg_decl_or_val, value_id,
-                                     insert_before) != nullptr;
+    modified |= AddDebugValueForDecl(dbg_decl_or_val, value_id, insert_before,
+                                     scope_and_line) != nullptr;
   }
   return modified;
 }
 
 Instruction* DebugInfoManager::AddDebugValueForDecl(
-    Instruction* dbg_decl, uint32_t value_id, Instruction* insert_before) {
+    Instruction* dbg_decl, uint32_t value_id, Instruction* insert_before,
+    Instruction* scope_and_line) {
   if (dbg_decl == nullptr || !IsDebugDeclare(dbg_decl)) return nullptr;
 
   std::unique_ptr<Instruction> dbg_val(dbg_decl->Clone(context()));
   dbg_val->SetResultId(context()->TakeNextId());
-  dbg_val->SetInOperand(kExtInstInstructionInIdx,
-                        {OpenCLDebugInfo100DebugValue});
+  dbg_val->SetInOperand(kExtInstInstructionInIdx, {CommonDebugInfoDebugValue});
   dbg_val->SetOperand(kDebugDeclareOperandVariableIndex, {value_id});
   dbg_val->SetOperand(kDebugValueOperandExpressionIndex,
                       {GetEmptyDebugExpression()->result_id()});
+  dbg_val->UpdateDebugInfoFrom(scope_and_line);
 
   auto* added_dbg_val = insert_before->InsertBefore(std::move(dbg_val));
   AnalyzeDebugInst(added_dbg_val);
@@ -530,9 +600,20 @@
   return added_dbg_val;
 }
 
+uint32_t DebugInfoManager::GetVulkanDebugOperation(Instruction* inst) {
+  assert(inst->GetShader100DebugOpcode() ==
+             NonSemanticShaderDebugInfo100DebugOperation &&
+         "inst must be Vulkan DebugOperation");
+  return context()
+      ->get_constant_mgr()
+      ->GetConstantFromInst(context()->get_def_use_mgr()->GetDef(
+          inst->GetSingleWordOperand(kDebugOperationOperandOperationIndex)))
+      ->GetU32();
+}
+
 uint32_t DebugInfoManager::GetVariableIdOfDebugValueUsedForDeclare(
     Instruction* inst) {
-  if (inst->GetOpenCL100DebugOpcode() != OpenCLDebugInfo100DebugValue) return 0;
+  if (inst->GetCommonDebugOpcode() != CommonDebugInfoDebugValue) return 0;
 
   auto* expr =
       GetDbgInst(inst->GetSingleWordOperand(kDebugValueOperandExpressionIndex));
@@ -542,9 +623,19 @@
   auto* operation = GetDbgInst(
       expr->GetSingleWordOperand(kDebugExpressOperandOperationIndex));
   if (operation == nullptr) return 0;
-  if (operation->GetSingleWordOperand(kDebugOperationOperandOperationIndex) !=
-      OpenCLDebugInfo100Deref) {
-    return 0;
+
+  // OpenCL.DebugInfo.100 contains a literal for the operation, Vulkan uses an
+  // OpConstant.
+  if (inst->IsOpenCL100DebugInstr()) {
+    if (operation->GetSingleWordOperand(kDebugOperationOperandOperationIndex) !=
+        OpenCLDebugInfo100Deref) {
+      return 0;
+    }
+  } else {
+    uint32_t operation_const = GetVulkanDebugOperation(operation);
+    if (operation_const != NonSemanticShaderDebugInfo100Deref) {
+      return 0;
+    }
   }
 
   uint32_t var_id =
@@ -565,8 +656,8 @@
 }
 
 bool DebugInfoManager::IsDebugDeclare(Instruction* instr) {
-  if (!instr->IsOpenCL100DebugInstr()) return false;
-  return instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare ||
+  if (!instr->IsCommonDebugInstr()) return false;
+  return instr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
          GetVariableIdOfDebugValueUsedForDeclare(instr) != 0;
 }
 
@@ -613,14 +704,13 @@
     users.insert(inst);
   }
 
-  if (!inst->IsOpenCL100DebugInstr()) return;
+  if (!inst->IsCommonDebugInstr()) return;
 
   RegisterDbgInst(inst);
 
-  if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) {
-    assert(GetDebugFunction(inst->GetSingleWordOperand(
-               kDebugFunctionOperandFunctionIndex)) == nullptr &&
-           "Two DebugFunction instruction exists for a single OpFunction.");
+  if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction ||
+      inst->GetShader100DebugOpcode() ==
+          NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
     RegisterDbgFunction(inst);
   }
 
@@ -631,8 +721,17 @@
     deref_operation_ = inst;
   }
 
+  if (deref_operation_ == nullptr &&
+      inst->GetShader100DebugOpcode() ==
+          NonSemanticShaderDebugInfo100DebugOperation) {
+    uint32_t operation_const = GetVulkanDebugOperation(inst);
+    if (operation_const == NonSemanticShaderDebugInfo100Deref) {
+      deref_operation_ = inst;
+    }
+  }
+
   if (debug_info_none_inst_ == nullptr &&
-      inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugInfoNone) {
+      inst->GetCommonDebugOpcode() == CommonDebugInfoDebugInfoNone) {
     debug_info_none_inst_ = inst;
   }
 
@@ -640,7 +739,7 @@
     empty_debug_expr_inst_ = inst;
   }
 
-  if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) {
+  if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
     uint32_t var_id =
         inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
     RegisterDbgDeclare(var_id, inst);
@@ -653,8 +752,8 @@
 
 void DebugInfoManager::ConvertDebugGlobalToLocalVariable(
     Instruction* dbg_global_var, Instruction* local_var) {
-  if (dbg_global_var->GetOpenCL100DebugOpcode() !=
-      OpenCLDebugInfo100DebugGlobalVariable) {
+  if (dbg_global_var->GetCommonDebugOpcode() !=
+      CommonDebugInfoDebugGlobalVariable) {
     return;
   }
   assert(local_var->opcode() == SpvOpVariable ||
@@ -662,7 +761,7 @@
 
   // Convert |dbg_global_var| to DebugLocalVariable
   dbg_global_var->SetInOperand(kExtInstInstructionInIdx,
-                               {OpenCLDebugInfo100DebugLocalVariable});
+                               {CommonDebugInfoDebugLocalVariable});
   auto flags = dbg_global_var->GetSingleWordOperand(
       kDebugGlobalVariableOperandFlagsIndex);
   for (uint32_t i = dbg_global_var->NumInOperands() - 1;
@@ -678,20 +777,20 @@
       context(), SpvOpExtInst, context()->get_type_mgr()->GetVoidTypeId(),
       context()->TakeNextId(),
       {
-          {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-           {context()
-                ->get_feature_mgr()
-                ->GetExtInstImportId_OpenCL100DebugInfo()}},
+          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
-           {static_cast<uint32_t>(OpenCLDebugInfo100DebugDeclare)}},
+           {static_cast<uint32_t>(CommonDebugInfoDebugDeclare)}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
            {dbg_global_var->result_id()}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {local_var->result_id()}},
           {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
            {GetEmptyDebugExpression()->result_id()}},
       }));
-  auto* added_dbg_decl =
-      local_var->NextNode()->InsertBefore(std::move(new_dbg_decl));
+  // Must insert after all OpVariables in block
+  Instruction* insert_before = local_var;
+  while (insert_before->opcode() == SpvOpVariable)
+    insert_before = insert_before->NextNode();
+  auto* added_dbg_decl = insert_before->InsertBefore(std::move(new_dbg_decl));
   if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
     context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_decl);
   if (context()->AreAnalysesValid(
@@ -711,7 +810,7 @@
   // list.
   if (empty_debug_expr_inst_ != nullptr &&
       empty_debug_expr_inst_->PreviousNode() != nullptr &&
-      empty_debug_expr_inst_->PreviousNode()->IsOpenCL100DebugInstr()) {
+      empty_debug_expr_inst_->PreviousNode()->IsCommonDebugInstr()) {
     empty_debug_expr_inst_->InsertBefore(
         &*context()->module()->ext_inst_debuginfo_begin());
   }
@@ -720,7 +819,7 @@
   // list.
   if (debug_info_none_inst_ != nullptr &&
       debug_info_none_inst_->PreviousNode() != nullptr &&
-      debug_info_none_inst_->PreviousNode()->IsOpenCL100DebugInstr()) {
+      debug_info_none_inst_->PreviousNode()->IsCommonDebugInstr()) {
     debug_info_none_inst_->InsertBefore(
         &*context()->module()->ext_inst_debuginfo_begin());
   }
@@ -738,7 +837,7 @@
     inlinedat_id_to_users_itr->second.erase(instr);
   }
 
-  if (instr == nullptr || !instr->IsOpenCL100DebugInstr()) {
+  if (instr == nullptr || !instr->IsCommonDebugInstr()) {
     return;
   }
 
@@ -749,9 +848,15 @@
         instr->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex);
     fn_id_to_dbg_fn_.erase(fn_id);
   }
+  if (instr->GetShader100DebugOpcode() ==
+      NonSemanticShaderDebugInfo100DebugFunction) {
+    auto fn_id = instr->GetSingleWordOperand(
+        kDebugFunctionDefinitionOperandOpFunctionIndex);
+    fn_id_to_dbg_fn_.erase(fn_id);
+  }
 
-  if (instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare ||
-      instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue) {
+  if (instr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
+      instr->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) {
     auto var_or_value_id =
         instr->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
     auto dbg_decl_itr = var_id_to_dbg_decl_.find(var_or_value_id);
@@ -765,6 +870,8 @@
     for (auto dbg_instr_itr = context()->module()->ext_inst_debuginfo_begin();
          dbg_instr_itr != context()->module()->ext_inst_debuginfo_end();
          ++dbg_instr_itr) {
+      // OpenCL.DebugInfo.100 contains the operation as a literal operand, in
+      // Vulkan it's referenced as an OpConstant.
       if (instr != &*dbg_instr_itr &&
           dbg_instr_itr->GetOpenCL100DebugOpcode() ==
               OpenCLDebugInfo100DebugOperation &&
@@ -773,6 +880,14 @@
               OpenCLDebugInfo100Deref) {
         deref_operation_ = &*dbg_instr_itr;
         break;
+      } else if (instr != &*dbg_instr_itr &&
+                 dbg_instr_itr->GetShader100DebugOpcode() ==
+                     NonSemanticShaderDebugInfo100DebugOperation) {
+        uint32_t operation_const = GetVulkanDebugOperation(&*dbg_instr_itr);
+        if (operation_const == NonSemanticShaderDebugInfo100Deref) {
+          deref_operation_ = &*dbg_instr_itr;
+          break;
+        }
       }
     }
   }
@@ -782,9 +897,8 @@
     for (auto dbg_instr_itr = context()->module()->ext_inst_debuginfo_begin();
          dbg_instr_itr != context()->module()->ext_inst_debuginfo_end();
          ++dbg_instr_itr) {
-      if (instr != &*dbg_instr_itr &&
-          dbg_instr_itr->GetOpenCL100DebugOpcode() ==
-              OpenCLDebugInfo100DebugInfoNone) {
+      if (instr != &*dbg_instr_itr && dbg_instr_itr->GetCommonDebugOpcode() ==
+                                          CommonDebugInfoDebugInfoNone) {
         debug_info_none_inst_ = &*dbg_instr_itr;
         break;
       }
diff --git a/source/opt/debug_info_manager.h b/source/opt/debug_info_manager.h
index 92b3818..df34b30 100644
--- a/source/opt/debug_info_manager.h
+++ b/source/opt/debug_info_manager.h
@@ -67,8 +67,8 @@
   std::unordered_map<uint32_t, uint32_t> callee_inlined_at2chain_;
 };
 
-// A class for analyzing, managing, and creating OpenCL.DebugInfo.100 extension
-// instructions.
+// A class for analyzing, managing, and creating OpenCL.DebugInfo.100 and
+// NonSemantic.Shader.DebugInfo.100 extension instructions.
 class DebugInfoManager {
  public:
   // Constructs a debug information manager from the given |context|.
@@ -85,7 +85,7 @@
     return !(lhs == rhs);
   }
 
-  // Analyzes OpenCL.DebugInfo.100 instruction |dbg_inst|.
+  // Analyzes DebugInfo instruction |dbg_inst|.
   void AnalyzeDebugInst(Instruction* dbg_inst);
 
   // Creates new DebugInlinedAt and returns its id. Its line operand is the
@@ -152,15 +152,21 @@
       std::unordered_set<Instruction*>* invisible_decls);
 
   // Creates a DebugValue for DebugDeclare |dbg_decl| and inserts it before
-  // |insert_before|. The new DebugValue has the same line, scope, and
-  // operands with DebugDeclare but it uses |value_id| for value. Returns
-  // the added DebugValue, or nullptr if it does not add a DebugValue.
+  // |insert_before|. The new DebugValue has the same line and scope as
+  // |scope_and_line|, or no scope and line information if |scope_and_line|
+  // is nullptr. The new DebugValue has the same operands as DebugDeclare
+  // but it uses |value_id| for the value. Returns the created DebugValue,
+  // or nullptr if fails to create one.
   Instruction* AddDebugValueForDecl(Instruction* dbg_decl, uint32_t value_id,
-                                    Instruction* insert_before);
+                                    Instruction* insert_before,
+                                    Instruction* scope_and_line);
 
   // Erases |instr| from data structures of this class.
   void ClearDebugInfo(Instruction* instr);
 
+  // Return the opcode for the Vulkan DebugOperation inst
+  uint32_t GetVulkanDebugOperation(Instruction* inst);
+
   // Returns the id of Value operand if |inst| is DebugValue who has Deref
   // operation and its Value operand is a result id of OpVariable with
   // Function storage class. Otherwise, returns 0.
@@ -187,10 +193,13 @@
  private:
   IRContext* context() { return context_; }
 
-  // Analyzes OpenCL.DebugInfo.100 instructions in the given |module| and
+  // Analyzes DebugInfo instructions in the given |module| and
   // populates data structures in this class.
   void AnalyzeDebugInsts(Module& module);
 
+  // Get the DebugInfo ExtInstImport Id, or 0 if no DebugInfo is available.
+  uint32_t GetDbgSetImportId();
+
   // Returns the debug instruction whose id is |id|. Returns |nullptr| if one
   // does not exists.
   Instruction* GetDbgInst(uint32_t id);
@@ -227,7 +236,7 @@
 
   IRContext* context_;
 
-  // Mapping from ids of OpenCL.DebugInfo.100 extension instructions
+  // Mapping from ids of DebugInfo extension instructions.
   // to their Instruction instances.
   std::unordered_map<uint32_t, Instruction*> id_to_dbg_inst_;
 
diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp
index 8b4aee5..2146c35 100644
--- a/source/opt/decoration_manager.cpp
+++ b/source/opt/decoration_manager.cpp
@@ -53,11 +53,12 @@
 namespace opt {
 namespace analysis {
 
-void DecorationManager::RemoveDecorationsFrom(
+bool DecorationManager::RemoveDecorationsFrom(
     uint32_t id, std::function<bool(const Instruction&)> pred) {
+  bool was_modified = false;
   const auto ids_iter = id_to_decoration_insts_.find(id);
   if (ids_iter == id_to_decoration_insts_.end()) {
-    return;
+    return was_modified;
   }
 
   TargetData& decorations_info = ids_iter->second;
@@ -99,7 +100,6 @@
 
     // Otherwise, remove |id| from the targets of |group_id|
     const uint32_t stride = inst->opcode() == SpvOpGroupDecorate ? 1u : 2u;
-    bool was_modified = false;
     for (uint32_t i = 1u; i < inst->NumInOperands();) {
       if (inst->GetSingleWordInOperand(i) != id) {
         i += stride;
@@ -155,6 +155,7 @@
           }),
       indirect_decorations.end());
 
+  was_modified |= !insts_to_kill.empty();
   for (Instruction* inst : insts_to_kill) context->KillInst(inst);
   insts_to_kill.clear();
 
@@ -165,6 +166,7 @@
     for (Instruction* inst : decorations_info.decorate_insts)
       insts_to_kill.push_back(inst);
   }
+  was_modified |= !insts_to_kill.empty();
   for (Instruction* inst : insts_to_kill) context->KillInst(inst);
 
   if (decorations_info.direct_decorations.empty() &&
@@ -172,6 +174,7 @@
       decorations_info.decorate_insts.empty()) {
     id_to_decoration_insts_.erase(ids_iter);
   }
+  return was_modified;
 }
 
 std::vector<Instruction*> DecorationManager::GetDecorationsFor(
@@ -487,6 +490,14 @@
   });
 }
 
+bool DecorationManager::HasDecoration(uint32_t id, uint32_t decoration) {
+  bool has_decoration = false;
+  ForEachDecoration(id, decoration, [&has_decoration](const Instruction&) {
+    has_decoration = true;
+  });
+  return has_decoration;
+}
+
 bool DecorationManager::FindDecoration(
     uint32_t id, uint32_t decoration,
     std::function<bool(const Instruction&)> f) {
diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h
index e1ae8d5..fe78f2c 100644
--- a/source/opt/decoration_manager.h
+++ b/source/opt/decoration_manager.h
@@ -36,8 +36,9 @@
   }
   DecorationManager() = delete;
 
-  // Changes all of the decorations (direct and through groups) where |pred| is
-  // true and that apply to |id| so that they no longer apply to |id|.
+  // Removes all decorations (direct and through groups) where |pred| is
+  // true and that apply to |id| so that they no longer apply to |id|.  Returns
+  // true if something changed.
   //
   // If |id| is part of a group, it will be removed from the group if it
   // does not use all of the group's decorations, or, if there are no
@@ -52,9 +53,9 @@
   // removed, then the |OpGroupDecorate| and
   // |OpGroupMemberDecorate| for the group will be killed, but not the defining
   // |OpDecorationGroup| instruction.
-  void RemoveDecorationsFrom(uint32_t id,
-                             std::function<bool(const Instruction&)> pred =
-                                 [](const Instruction&) { return true; });
+  bool RemoveDecorationsFrom(
+      uint32_t id, std::function<bool(const Instruction&)> pred =
+                       [](const Instruction&) { return true; });
 
   // Removes all decorations from the result id of |inst|.
   //
@@ -89,6 +90,10 @@
   bool AreDecorationsTheSame(const Instruction* inst1, const Instruction* inst2,
                              bool ignore_target) const;
 
+  // Returns whether a decoration instruction for |id| with decoration
+  // |decoration| exists or not.
+  bool HasDecoration(uint32_t id, uint32_t decoration);
+
   // |f| is run on each decoration instruction for |id| with decoration
   // |decoration|. Processed are all decorations which target |id| either
   // directly or indirectly by Decoration Groups.
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index 0ec98ca..394b9fa 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -58,7 +58,7 @@
       case SPV_OPERAND_TYPE_SCOPE_ID: {
         uint32_t use_id = inst->GetSingleWordOperand(i);
         Instruction* def = GetDef(use_id);
-        assert(def && "Definition is not registered.");
+        if (!def) assert(false && "Definition is not registered.");
         id_to_users_.insert(UserEntry(def, inst));
         used_ids->push_back(use_id);
       } break;
@@ -71,6 +71,9 @@
 void DefUseManager::AnalyzeInstDefUse(Instruction* inst) {
   AnalyzeInstDef(inst);
   AnalyzeInstUse(inst);
+  // Analyze lines last otherwise they will be cleared when inst is
+  // cleared by preceding two calls
+  for (auto& l_inst : inst->dbg_line_insts()) AnalyzeInstDefUse(&l_inst);
 }
 
 void DefUseManager::UpdateDefUse(Instruction* inst) {
@@ -224,9 +227,11 @@
   if (!module) return;
   // Analyze all the defs before any uses to catch forward references.
   module->ForEachInst(
-      std::bind(&DefUseManager::AnalyzeInstDef, this, std::placeholders::_1));
+      std::bind(&DefUseManager::AnalyzeInstDef, this, std::placeholders::_1),
+      true);
   module->ForEachInst(
-      std::bind(&DefUseManager::AnalyzeInstUse, this, std::placeholders::_1));
+      std::bind(&DefUseManager::AnalyzeInstUse, this, std::placeholders::_1),
+      true);
 }
 
 void DefUseManager::ClearInst(Instruction* inst) {
@@ -261,6 +266,16 @@
 
 bool operator==(const DefUseManager& lhs, const DefUseManager& rhs) {
   if (lhs.id_to_def_ != rhs.id_to_def_) {
+    for (auto p : lhs.id_to_def_) {
+      if (rhs.id_to_def_.find(p.first) == rhs.id_to_def_.end()) {
+        return false;
+      }
+    }
+    for (auto p : rhs.id_to_def_) {
+      if (lhs.id_to_def_.find(p.first) == lhs.id_to_def_.end()) {
+        return false;
+      }
+    }
     return false;
   }
 
diff --git a/source/opt/desc_sroa.cpp b/source/opt/desc_sroa.cpp
index 5e95006..bcbdde9 100644
--- a/source/opt/desc_sroa.cpp
+++ b/source/opt/desc_sroa.cpp
@@ -14,10 +14,19 @@
 
 #include "source/opt/desc_sroa.h"
 
+#include "source/opt/desc_sroa_util.h"
 #include "source/util/string_utils.h"
 
 namespace spvtools {
 namespace opt {
+namespace {
+
+bool IsDecorationBinding(Instruction* inst) {
+  if (inst->opcode() != SpvOpDecorate) return false;
+  return inst->GetSingleWordInOperand(1u) == SpvDecorationBinding;
+}
+
+}  // namespace
 
 Pass::Status DescriptorScalarReplacement::Process() {
   bool modified = false;
@@ -25,7 +34,7 @@
   std::vector<Instruction*> vars_to_kill;
 
   for (Instruction& var : context()->types_values()) {
-    if (IsCandidate(&var)) {
+    if (descsroautil::IsDescriptorArray(context(), &var)) {
       modified = true;
       if (!ReplaceCandidate(&var)) {
         return Status::Failure;
@@ -41,72 +50,6 @@
   return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
 }
 
-bool DescriptorScalarReplacement::IsCandidate(Instruction* var) {
-  if (var->opcode() != SpvOpVariable) {
-    return false;
-  }
-
-  uint32_t ptr_type_id = var->type_id();
-  Instruction* ptr_type_inst =
-      context()->get_def_use_mgr()->GetDef(ptr_type_id);
-  if (ptr_type_inst->opcode() != SpvOpTypePointer) {
-    return false;
-  }
-
-  uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
-  Instruction* var_type_inst =
-      context()->get_def_use_mgr()->GetDef(var_type_id);
-  if (var_type_inst->opcode() != SpvOpTypeArray &&
-      var_type_inst->opcode() != SpvOpTypeStruct) {
-    return false;
-  }
-
-  // All structures with descriptor assignments must be replaced by variables,
-  // one for each of their members - with the exceptions of buffers.
-  if (IsTypeOfStructuredBuffer(var_type_inst)) {
-    return false;
-  }
-
-  bool has_desc_set_decoration = false;
-  context()->get_decoration_mgr()->ForEachDecoration(
-      var->result_id(), SpvDecorationDescriptorSet,
-      [&has_desc_set_decoration](const Instruction&) {
-        has_desc_set_decoration = true;
-      });
-  if (!has_desc_set_decoration) {
-    return false;
-  }
-
-  bool has_binding_decoration = false;
-  context()->get_decoration_mgr()->ForEachDecoration(
-      var->result_id(), SpvDecorationBinding,
-      [&has_binding_decoration](const Instruction&) {
-        has_binding_decoration = true;
-      });
-  if (!has_binding_decoration) {
-    return false;
-  }
-
-  return true;
-}
-
-bool DescriptorScalarReplacement::IsTypeOfStructuredBuffer(
-    const Instruction* type) const {
-  if (type->opcode() != SpvOpTypeStruct) {
-    return false;
-  }
-
-  // All buffers have offset decorations for members of their structure types.
-  // This is how we distinguish it from a structure of descriptors.
-  bool has_offset_decoration = false;
-  context()->get_decoration_mgr()->ForEachDecoration(
-      type->result_id(), SpvDecorationOffset,
-      [&has_offset_decoration](const Instruction&) {
-        has_offset_decoration = true;
-      });
-  return has_offset_decoration;
-}
-
 bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) {
   std::vector<Instruction*> access_chain_work_list;
   std::vector<Instruction*> load_work_list;
@@ -162,16 +105,15 @@
     return false;
   }
 
-  uint32_t idx_id = use->GetSingleWordInOperand(1);
-  const analysis::Constant* idx_const =
-      context()->get_constant_mgr()->FindDeclaredConstant(idx_id);
-  if (idx_const == nullptr) {
+  const analysis::Constant* const_index =
+      descsroautil::GetAccessChainIndexAsConst(context(), use);
+  if (const_index == nullptr) {
     context()->EmitErrorMessage("Variable cannot be replaced: invalid index",
                                 use);
     return false;
   }
 
-  uint32_t idx = idx_const->GetU32();
+  uint32_t idx = const_index->GetU32();
   uint32_t replacement_var = GetReplacementVariable(var, idx);
 
   if (use->NumInOperands() == 2) {
@@ -208,39 +150,12 @@
                                                              uint32_t idx) {
   auto replacement_vars = replacement_variables_.find(var);
   if (replacement_vars == replacement_variables_.end()) {
-    uint32_t ptr_type_id = var->type_id();
-    Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
-    assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
-           "Variable should be a pointer to an array or structure.");
-    uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
-    Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id);
-    const bool is_array = pointee_type_inst->opcode() == SpvOpTypeArray;
-    const bool is_struct = pointee_type_inst->opcode() == SpvOpTypeStruct;
-    assert((is_array || is_struct) &&
-           "Variable should be a pointer to an array or structure.");
-
-    // For arrays, each array element should be replaced with a new replacement
-    // variable
-    if (is_array) {
-      uint32_t array_len_id = pointee_type_inst->GetSingleWordInOperand(1);
-      const analysis::Constant* array_len_const =
-          context()->get_constant_mgr()->FindDeclaredConstant(array_len_id);
-      assert(array_len_const != nullptr && "Array length must be a constant.");
-      uint32_t array_len = array_len_const->GetU32();
-
-      replacement_vars = replacement_variables_
-                             .insert({var, std::vector<uint32_t>(array_len, 0)})
-                             .first;
-    }
-    // For structures, each member should be replaced with a new replacement
-    // variable
-    if (is_struct) {
-      const uint32_t num_members = pointee_type_inst->NumInOperands();
-      replacement_vars =
-          replacement_variables_
-              .insert({var, std::vector<uint32_t>(num_members, 0)})
-              .first;
-    }
+    uint32_t number_of_elements =
+        descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
+    replacement_vars =
+        replacement_variables_
+            .insert({var, std::vector<uint32_t>(number_of_elements, 0)})
+            .first;
   }
 
   if (replacement_vars->second[idx] == 0) {
@@ -250,6 +165,74 @@
   return replacement_vars->second[idx];
 }
 
+void DescriptorScalarReplacement::CopyDecorationsForNewVariable(
+    Instruction* old_var, uint32_t index, uint32_t new_var_id,
+    uint32_t new_var_ptr_type_id, const bool is_old_var_array,
+    const bool is_old_var_struct, Instruction* old_var_type) {
+  // Handle OpDecorate instructions.
+  for (auto old_decoration :
+       get_decoration_mgr()->GetDecorationsFor(old_var->result_id(), true)) {
+    uint32_t new_binding = 0;
+    if (IsDecorationBinding(old_decoration)) {
+      new_binding = GetNewBindingForElement(
+          old_decoration->GetSingleWordInOperand(2), index, new_var_ptr_type_id,
+          is_old_var_array, is_old_var_struct, old_var_type);
+    }
+    CreateNewDecorationForNewVariable(old_decoration, new_var_id, new_binding);
+  }
+
+  // Handle OpMemberDecorate instructions.
+  for (auto old_decoration : get_decoration_mgr()->GetDecorationsFor(
+           old_var_type->result_id(), true)) {
+    assert(old_decoration->opcode() == SpvOpMemberDecorate);
+    if (old_decoration->GetSingleWordInOperand(1u) != index) continue;
+    CreateNewDecorationForMemberDecorate(old_decoration, new_var_id);
+  }
+}
+
+uint32_t DescriptorScalarReplacement::GetNewBindingForElement(
+    uint32_t old_binding, uint32_t index, uint32_t new_var_ptr_type_id,
+    const bool is_old_var_array, const bool is_old_var_struct,
+    Instruction* old_var_type) {
+  if (is_old_var_array) {
+    return old_binding + index * GetNumBindingsUsedByType(new_var_ptr_type_id);
+  }
+  if (is_old_var_struct) {
+    // The binding offset that should be added is the sum of binding
+    // numbers used by previous members of the current struct.
+    uint32_t new_binding = old_binding;
+    for (uint32_t i = 0; i < index; ++i) {
+      new_binding +=
+          GetNumBindingsUsedByType(old_var_type->GetSingleWordInOperand(i));
+    }
+    return new_binding;
+  }
+  return old_binding;
+}
+
+void DescriptorScalarReplacement::CreateNewDecorationForNewVariable(
+    Instruction* old_decoration, uint32_t new_var_id, uint32_t new_binding) {
+  assert(old_decoration->opcode() == SpvOpDecorate);
+  std::unique_ptr<Instruction> new_decoration(old_decoration->Clone(context()));
+  new_decoration->SetInOperand(0, {new_var_id});
+
+  if (IsDecorationBinding(new_decoration.get())) {
+    new_decoration->SetInOperand(2, {new_binding});
+  }
+  context()->AddAnnotationInst(std::move(new_decoration));
+}
+
+void DescriptorScalarReplacement::CreateNewDecorationForMemberDecorate(
+    Instruction* old_member_decoration, uint32_t new_var_id) {
+  std::vector<Operand> operands(
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {new_var_id}}});
+  auto new_decorate_operand_begin = old_member_decoration->begin() + 2u;
+  auto new_decorate_operand_end = old_member_decoration->end();
+  operands.insert(operands.end(), new_decorate_operand_begin,
+                  new_decorate_operand_end);
+  get_decoration_mgr()->AddDecoration(SpvOpDecorate, std::move(operands));
+}
+
 uint32_t DescriptorScalarReplacement::CreateReplacementVariable(
     Instruction* var, uint32_t idx) {
   // The storage class for the new variable is the same as the original.
@@ -285,33 +268,8 @@
                            {static_cast<uint32_t>(storage_class)}}}));
   context()->AddGlobalValue(std::move(variable));
 
-  // Copy all of the decorations to the new variable.  The only difference is
-  // the Binding decoration needs to be adjusted.
-  for (auto old_decoration :
-       get_decoration_mgr()->GetDecorationsFor(var->result_id(), true)) {
-    assert(old_decoration->opcode() == SpvOpDecorate);
-    std::unique_ptr<Instruction> new_decoration(
-        old_decoration->Clone(context()));
-    new_decoration->SetInOperand(0, {id});
-
-    uint32_t decoration = new_decoration->GetSingleWordInOperand(1u);
-    if (decoration == SpvDecorationBinding) {
-      uint32_t new_binding = new_decoration->GetSingleWordInOperand(2);
-      if (is_array) {
-        new_binding += idx * GetNumBindingsUsedByType(ptr_element_type_id);
-      }
-      if (is_struct) {
-        // The binding offset that should be added is the sum of binding numbers
-        // used by previous members of the current struct.
-        for (uint32_t i = 0; i < idx; ++i) {
-          new_binding += GetNumBindingsUsedByType(
-              pointee_type_inst->GetSingleWordInOperand(i));
-        }
-      }
-      new_decoration->SetInOperand(2, {new_binding});
-    }
-    context()->AddAnnotationInst(std::move(new_decoration));
-  }
+  CopyDecorationsForNewVariable(var, idx, id, ptr_element_type_id, is_array,
+                                is_struct, pointee_type_inst);
 
   // Create a new OpName for the replacement variable.
   std::vector<std::unique_ptr<Instruction>> names_to_add;
@@ -377,7 +335,7 @@
   // The number of bindings consumed by a structure is the sum of the bindings
   // used by its members.
   if (type_inst->opcode() == SpvOpTypeStruct &&
-      !IsTypeOfStructuredBuffer(type_inst)) {
+      !descsroautil::IsTypeOfStructuredBuffer(context(), type_inst)) {
     uint32_t sum = 0;
     for (uint32_t i = 0; i < type_inst->NumInOperands(); i++)
       sum += GetNumBindingsUsedByType(type_inst->GetSingleWordInOperand(i));
diff --git a/source/opt/desc_sroa.h b/source/opt/desc_sroa.h
index cd72fd3..fea0625 100644
--- a/source/opt/desc_sroa.h
+++ b/source/opt/desc_sroa.h
@@ -46,10 +46,6 @@
   }
 
  private:
-  // Returns true if |var| is an OpVariable instruction that represents a
-  // descriptor array.  These are the variables that we want to replace.
-  bool IsCandidate(Instruction* var);
-
   // Replaces all references to |var| by new variables, one for each element of
   // the array |var|.  The binding for the new variables corresponding to
   // element i will be the binding of |var| plus i.  Returns true if successful.
@@ -93,10 +89,45 @@
   // bindings used by its members.
   uint32_t GetNumBindingsUsedByType(uint32_t type_id);
 
-  // Returns true if |type| is a type that could be used for a structured buffer
-  // as opposed to a type that would be used for a structure of resource
-  // descriptors.
-  bool IsTypeOfStructuredBuffer(const Instruction* type) const;
+  // Copy all of the decorations of variable |old_var| and make them as
+  // decorations for the new variable whose id is |new_var_id|. The new variable
+  // is supposed to replace |index|th element of |old_var|.
+  // |new_var_ptr_type_id| is the id of the pointer to the type of the new
+  // variable. |is_old_var_array| is true if |old_var| has an array type.
+  // |is_old_var_struct| is true if |old_var| has a structure type.
+  // |old_var_type| is the pointee type of |old_var|.
+  void CopyDecorationsForNewVariable(Instruction* old_var, uint32_t index,
+                                     uint32_t new_var_id,
+                                     uint32_t new_var_ptr_type_id,
+                                     const bool is_old_var_array,
+                                     const bool is_old_var_struct,
+                                     Instruction* old_var_type);
+
+  // Get the new binding number for a new variable that will be replaced with an
+  // |index|th element of an old variable. The old variable has |old_binding|
+  // as its binding number. |ptr_elem_type_id| the id of the pointer to the
+  // element type. |is_old_var_array| is true if the old variable has an array
+  // type. |is_old_var_struct| is true if the old variable has a structure type.
+  // |old_var_type| is the pointee type of the old variable.
+  uint32_t GetNewBindingForElement(uint32_t old_binding, uint32_t index,
+                                   uint32_t ptr_elem_type_id,
+                                   const bool is_old_var_array,
+                                   const bool is_old_var_struct,
+                                   Instruction* old_var_type);
+
+  // Create a new OpDecorate instruction by cloning |old_decoration|. The new
+  // OpDecorate instruction will be used for a variable whose id is
+  // |new_var_ptr_type_id|. If |old_decoration| is a decoration for a binding,
+  // the new OpDecorate instruction will have |new_binding| as its binding.
+  void CreateNewDecorationForNewVariable(Instruction* old_decoration,
+                                         uint32_t new_var_id,
+                                         uint32_t new_binding);
+
+  // Create a new OpDecorate instruction whose operand is the same as an
+  // OpMemberDecorate instruction |old_member_decoration| except Target operand.
+  // The Target operand of the new OpDecorate instruction will be |new_var_id|.
+  void CreateNewDecorationForMemberDecorate(Instruction* old_decoration,
+                                            uint32_t new_var_id);
 
   // A map from an OpVariable instruction to the set of variables that will be
   // used to replace it. The entry |replacement_variables_[var][i]| is the id of
diff --git a/source/opt/desc_sroa_util.cpp b/source/opt/desc_sroa_util.cpp
new file mode 100644
index 0000000..1954e2c
--- /dev/null
+++ b/source/opt/desc_sroa_util.cpp
@@ -0,0 +1,117 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/desc_sroa_util.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+const uint32_t kOpAccessChainInOperandIndexes = 1;
+
+// Returns the length of array type |type|.
+uint32_t GetLengthOfArrayType(IRContext* context, Instruction* type) {
+  assert(type->opcode() == SpvOpTypeArray && "type must be array");
+  uint32_t length_id = type->GetSingleWordInOperand(1);
+  const analysis::Constant* length_const =
+      context->get_constant_mgr()->FindDeclaredConstant(length_id);
+  assert(length_const != nullptr);
+  return length_const->GetU32();
+}
+
+}  // namespace
+
+namespace descsroautil {
+
+bool IsDescriptorArray(IRContext* context, Instruction* var) {
+  if (var->opcode() != SpvOpVariable) {
+    return false;
+  }
+
+  uint32_t ptr_type_id = var->type_id();
+  Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
+  if (ptr_type_inst->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+
+  uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1);
+  Instruction* var_type_inst = context->get_def_use_mgr()->GetDef(var_type_id);
+  if (var_type_inst->opcode() != SpvOpTypeArray &&
+      var_type_inst->opcode() != SpvOpTypeStruct) {
+    return false;
+  }
+
+  // All structures with descriptor assignments must be replaced by variables,
+  // one for each of their members - with the exceptions of buffers.
+  if (IsTypeOfStructuredBuffer(context, var_type_inst)) {
+    return false;
+  }
+
+  if (!context->get_decoration_mgr()->HasDecoration(
+          var->result_id(), SpvDecorationDescriptorSet)) {
+    return false;
+  }
+
+  return context->get_decoration_mgr()->HasDecoration(var->result_id(),
+                                                      SpvDecorationBinding);
+}
+
+bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type) {
+  if (type->opcode() != SpvOpTypeStruct) {
+    return false;
+  }
+
+  // All buffers have offset decorations for members of their structure types.
+  // This is how we distinguish it from a structure of descriptors.
+  return context->get_decoration_mgr()->HasDecoration(type->result_id(),
+                                                      SpvDecorationOffset);
+}
+
+const analysis::Constant* GetAccessChainIndexAsConst(
+    IRContext* context, Instruction* access_chain) {
+  if (access_chain->NumInOperands() <= 1) {
+    return nullptr;
+  }
+  uint32_t idx_id = GetFirstIndexOfAccessChain(access_chain);
+  const analysis::Constant* idx_const =
+      context->get_constant_mgr()->FindDeclaredConstant(idx_id);
+  return idx_const;
+}
+
+uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) {
+  assert(access_chain->NumInOperands() > 1 &&
+         "OpAccessChain does not have Indexes operand");
+  return access_chain->GetSingleWordInOperand(kOpAccessChainInOperandIndexes);
+}
+
+uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context,
+                                             Instruction* var) {
+  uint32_t ptr_type_id = var->type_id();
+  Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id);
+  assert(ptr_type_inst->opcode() == SpvOpTypePointer &&
+         "Variable should be a pointer to an array or structure.");
+  uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1);
+  Instruction* pointee_type_inst =
+      context->get_def_use_mgr()->GetDef(pointee_type_id);
+  if (pointee_type_inst->opcode() == SpvOpTypeArray) {
+    return GetLengthOfArrayType(context, pointee_type_inst);
+  }
+  assert(pointee_type_inst->opcode() == SpvOpTypeStruct &&
+         "Variable should be a pointer to an array or structure.");
+  return pointee_type_inst->NumInOperands();
+}
+
+}  // namespace descsroautil
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/desc_sroa_util.h b/source/opt/desc_sroa_util.h
new file mode 100644
index 0000000..2f45c0c
--- /dev/null
+++ b/source/opt/desc_sroa_util.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_DESC_SROA_UTIL_H_
+#define SOURCE_OPT_DESC_SROA_UTIL_H_
+
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// Provides functions for the descriptor array SROA.
+namespace descsroautil {
+
+// Returns true if |var| is an OpVariable instruction that represents a
+// descriptor array.
+bool IsDescriptorArray(IRContext* context, Instruction* var);
+
+// Returns true if |type| is a type that could be used for a structured buffer
+// as opposed to a type that would be used for a structure of resource
+// descriptors.
+bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type);
+
+// Returns the first index of the OpAccessChain instruction |access_chain| as
+// a constant. Returns nullptr if it is not a constant.
+const analysis::Constant* GetAccessChainIndexAsConst(IRContext* context,
+                                                     Instruction* access_chain);
+
+// Returns the number of elements of an OpVariable instruction |var| whose type
+// must be a pointer to an array or a struct.
+uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context,
+                                             Instruction* var);
+
+// Returns the first Indexes operand id of the OpAccessChain or
+// OpInBoundsAccessChain instruction |access_chain|. The access chain must have
+// at least 1 index.
+uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain);
+
+}  // namespace descsroautil
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_DESC_SROA_UTIL_H_
diff --git a/source/opt/eliminate_dead_functions_util.cpp b/source/opt/eliminate_dead_functions_util.cpp
index 6b5234b..1379120 100644
--- a/source/opt/eliminate_dead_functions_util.cpp
+++ b/source/opt/eliminate_dead_functions_util.cpp
@@ -23,9 +23,11 @@
                                    Module::iterator* func_iter) {
   bool first_func = *func_iter == context->module()->begin();
   bool seen_func_end = false;
+  std::unordered_set<Instruction*> to_kill;
   (*func_iter)
       ->ForEachInst(
-          [context, first_func, func_iter, &seen_func_end](Instruction* inst) {
+          [context, first_func, func_iter, &seen_func_end,
+           &to_kill](Instruction* inst) {
             if (inst->opcode() == SpvOpFunctionEnd) {
               seen_func_end = true;
             }
@@ -33,6 +35,7 @@
             // global values if this is the first function.
             if (seen_func_end && inst->opcode() == SpvOpExtInst) {
               assert(inst->IsNonSemanticInstruction());
+              if (to_kill.find(inst) != to_kill.end()) return;
               std::unique_ptr<Instruction> clone(inst->Clone(context));
               context->ForgetUses(inst);
               context->AnalyzeDefUse(clone.get());
@@ -44,12 +47,17 @@
                 prev_func_iter->AddNonSemanticInstruction(std::move(clone));
               }
               inst->ToNop();
-            } else {
-              context->KillNonSemanticInfo(inst);
+            } else if (to_kill.find(inst) == to_kill.end()) {
+              context->CollectNonSemanticTree(inst, &to_kill);
               context->KillInst(inst);
             }
           },
           true, true);
+
+  for (auto* dead : to_kill) {
+    context->KillInst(dead);
+  }
+
   return func_iter->Erase();
 }
 
diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp
index 173df62..a24ba8f 100644
--- a/source/opt/eliminate_dead_members_pass.cpp
+++ b/source/opt/eliminate_dead_members_pass.cpp
@@ -20,6 +20,7 @@
 namespace {
 const uint32_t kRemovedMember = 0xFFFFFFFF;
 const uint32_t kSpecConstOpOpcodeIdx = 0;
+constexpr uint32_t kArrayElementTypeIdx = 0;
 }  // namespace
 
 namespace spvtools {
@@ -64,6 +65,10 @@
           MarkPointeeTypeAsFullUsed(inst.type_id());
           break;
         default:
+          // Ignore structured buffers as layout(offset) qualifiers cannot be
+          // applied to structure fields
+          if (inst.IsVulkanStorageBufferVariable())
+            MarkPointeeTypeAsFullUsed(inst.type_id());
           break;
       }
     }
@@ -136,18 +141,22 @@
 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));
+  switch (type_inst->opcode()) {
+    case SpvOpTypeStruct:
+      // Mark every member and its type as fully used.
+      for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+        used_members_[type_id].insert(i);
+        MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i));
+      }
+      break;
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      MarkTypeAsFullyUsed(
+          type_inst->GetSingleWordInOperand(kArrayElementTypeIdx));
+      break;
+    default:
+      break;
   }
 }
 
diff --git a/source/opt/feature_manager.cpp b/source/opt/feature_manager.cpp
index ad70c1e..39a4a34 100644
--- a/source/opt/feature_manager.cpp
+++ b/source/opt/feature_manager.cpp
@@ -80,6 +80,8 @@
   extinst_importid_GLSLstd450_ = module->GetExtInstImportId("GLSL.std.450");
   extinst_importid_OpenCL100DebugInfo_ =
       module->GetExtInstImportId("OpenCL.DebugInfo.100");
+  extinst_importid_Shader100DebugInfo_ =
+      module->GetExtInstImportId("NonSemantic.Shader.DebugInfo.100");
 }
 
 bool operator==(const FeatureManager& a, const FeatureManager& b) {
@@ -107,6 +109,11 @@
     return false;
   }
 
+  if (a.extinst_importid_Shader100DebugInfo_ !=
+      b.extinst_importid_Shader100DebugInfo_) {
+    return false;
+  }
+
   return true;
 }
 }  // namespace opt
diff --git a/source/opt/feature_manager.h b/source/opt/feature_manager.h
index 66d1cba..68c8e9a 100644
--- a/source/opt/feature_manager.h
+++ b/source/opt/feature_manager.h
@@ -55,6 +55,10 @@
     return extinst_importid_OpenCL100DebugInfo_;
   }
 
+  uint32_t GetExtInstImportId_Shader100DebugInfo() const {
+    return extinst_importid_Shader100DebugInfo_;
+  }
+
   friend bool operator==(const FeatureManager& a, const FeatureManager& b);
   friend bool operator!=(const FeatureManager& a, const FeatureManager& b) {
     return !(a == b);
@@ -92,6 +96,10 @@
   // Common OpenCL100DebugInfo external instruction import ids, cached
   // for performance.
   uint32_t extinst_importid_OpenCL100DebugInfo_ = 0;
+
+  // Common NonSemanticShader100DebugInfo external instruction import ids,
+  // cached for performance.
+  uint32_t extinst_importid_Shader100DebugInfo_ = 0;
 };
 
 }  // namespace opt
diff --git a/source/opt/fix_storage_class.cpp b/source/opt/fix_storage_class.cpp
index 03da0d0..04eb132 100644
--- a/source/opt/fix_storage_class.cpp
+++ b/source/opt/fix_storage_class.cpp
@@ -222,6 +222,16 @@
       uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst);
 
       if (obj_type_id != pointee_type_id) {
+        if (context()->get_type_mgr()->GetType(obj_type_id)->AsImage() &&
+            context()->get_type_mgr()->GetType(pointee_type_id)->AsImage()) {
+          // When storing an image, allow the type mismatch
+          // and let the later legalization passes eliminate the OpStore.
+          // This is to support assigning an image to a variable,
+          // where the assigned image does not have a pre-defined
+          // image format.
+          return false;
+        }
+
         uint32_t copy_id = GenerateCopy(obj_inst, pointee_type_id, inst);
         inst->SetInOperand(1, {copy_id});
         context()->UpdateDefUse(inst);
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 010eec9..4904f18 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -14,6 +14,7 @@
 
 #include "source/opt/folding_rules.h"
 
+#include <climits>
 #include <limits>
 #include <memory>
 #include <utility>
@@ -124,6 +125,66 @@
       inst->GetSingleWordInOperand(in_op));
 }
 
+std::vector<uint32_t> ExtractInts(uint64_t val) {
+  std::vector<uint32_t> words;
+  words.push_back(static_cast<uint32_t>(val));
+  words.push_back(static_cast<uint32_t>(val >> 32));
+  return words;
+}
+
+std::vector<uint32_t> GetWordsFromScalarIntConstant(
+    const analysis::IntConstant* c) {
+  assert(c != nullptr);
+  uint32_t width = c->type()->AsInteger()->width();
+  assert(width == 32 || width == 64);
+  if (width == 64) {
+    uint64_t uval = static_cast<uint64_t>(c->GetU64());
+    return ExtractInts(uval);
+  }
+  return {c->GetU32()};
+}
+
+std::vector<uint32_t> GetWordsFromScalarFloatConstant(
+    const analysis::FloatConstant* c) {
+  assert(c != nullptr);
+  uint32_t width = c->type()->AsFloat()->width();
+  assert(width == 32 || width == 64);
+  if (width == 64) {
+    utils::FloatProxy<double> result(c->GetDouble());
+    return result.GetWords();
+  }
+  utils::FloatProxy<float> result(c->GetFloat());
+  return result.GetWords();
+}
+
+std::vector<uint32_t> GetWordsFromNumericScalarOrVectorConstant(
+    analysis::ConstantManager* const_mgr, const analysis::Constant* c) {
+  if (const auto* float_constant = c->AsFloatConstant()) {
+    return GetWordsFromScalarFloatConstant(float_constant);
+  } else if (const auto* int_constant = c->AsIntConstant()) {
+    return GetWordsFromScalarIntConstant(int_constant);
+  } else if (const auto* vec_constant = c->AsVectorConstant()) {
+    std::vector<uint32_t> words;
+    for (const auto* comp : vec_constant->GetComponents()) {
+      auto comp_in_words =
+          GetWordsFromNumericScalarOrVectorConstant(const_mgr, comp);
+      words.insert(words.end(), comp_in_words.begin(), comp_in_words.end());
+    }
+    return words;
+  }
+  return {};
+}
+
+const analysis::Constant* ConvertWordsToNumericScalarOrVectorConstant(
+    analysis::ConstantManager* const_mgr, const std::vector<uint32_t>& words,
+    const analysis::Type* type) {
+  if (type->AsInteger() || type->AsFloat())
+    return const_mgr->GetConstant(type, words);
+  if (const auto* vec_type = type->AsVector())
+    return const_mgr->GetNumericVectorConstantWithWords(vec_type, words);
+  return nullptr;
+}
+
 // Returns the negation of |c|. |c| must be a 32 or 64 bit floating point
 // constant.
 uint32_t NegateFloatingPointConstant(analysis::ConstantManager* const_mgr,
@@ -146,13 +207,6 @@
   return const_mgr->GetDefiningInstruction(negated_const)->result_id();
 }
 
-std::vector<uint32_t> ExtractInts(uint64_t val) {
-  std::vector<uint32_t> words;
-  words.push_back(static_cast<uint32_t>(val));
-  words.push_back(static_cast<uint32_t>(val >> 32));
-  return words;
-}
-
 // Negates the integer constant |c|. Returns the id of the defining instruction.
 uint32_t NegateIntegerConstant(analysis::ConstantManager* const_mgr,
                                const analysis::Constant* c) {
@@ -470,7 +524,8 @@
     float fval = val.getAsFloat();                                           \
     if (!IsValidResult(fval)) return 0;                                      \
     words = val.GetWords();                                                  \
-  }
+  }                                                                          \
+  static_assert(true, "require extra semicolon")
   switch (opcode) {
     case SpvOpFMul:
       FOLD_OP(*);
@@ -505,24 +560,19 @@
   uint32_t width = type->AsInteger()->width();
   assert(width == 32 || width == 64);
   std::vector<uint32_t> words;
-#define FOLD_OP(op)                                        \
-  if (width == 64) {                                       \
-    if (type->IsSigned()) {                                \
-      int64_t val = input1->GetS64() op input2->GetS64();  \
-      words = ExtractInts(static_cast<uint64_t>(val));     \
-    } else {                                               \
-      uint64_t val = input1->GetU64() op input2->GetU64(); \
-      words = ExtractInts(val);                            \
-    }                                                      \
-  } else {                                                 \
-    if (type->IsSigned()) {                                \
-      int32_t val = input1->GetS32() op input2->GetS32();  \
-      words.push_back(static_cast<uint32_t>(val));         \
-    } else {                                               \
-      uint32_t val = input1->GetU32() op input2->GetU32(); \
-      words.push_back(val);                                \
-    }                                                      \
-  }
+  // Regardless of the sign of the constant, folding is performed on an unsigned
+  // interpretation of the constant data. This avoids signed integer overflow
+  // while folding, and works because sign is irrelevant for the IAdd, ISub and
+  // IMul instructions.
+#define FOLD_OP(op)                                      \
+  if (width == 64) {                                     \
+    uint64_t val = input1->GetU64() op input2->GetU64(); \
+    words = ExtractInts(val);                            \
+  } else {                                               \
+    uint32_t val = input1->GetU32() op input2->GetU32(); \
+    words.push_back(val);                                \
+  }                                                      \
+  static_assert(true, "require extra semicolon")
   switch (opcode) {
     case SpvOpIMul:
       FOLD_OP(*);
@@ -914,30 +964,21 @@
 // Fold divides of a constant and a negation.
 // Cases:
 // (-x) / 2 = x / -2
-// 2 / (-x) = 2 / -x
+// 2 / (-x) = -2 / x
 FoldingRule MergeDivNegateArithmetic() {
   return [](IRContext* context, Instruction* inst,
             const std::vector<const analysis::Constant*>& constants) {
-    assert(inst->opcode() == SpvOpFDiv || inst->opcode() == SpvOpSDiv ||
-           inst->opcode() == SpvOpUDiv);
+    assert(inst->opcode() == SpvOpFDiv);
     analysis::ConstantManager* const_mgr = context->get_constant_mgr();
-    const analysis::Type* type =
-        context->get_type_mgr()->GetType(inst->type_id());
-    bool uses_float = HasFloatingPoint(type);
-    if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false;
-
-    uint32_t width = ElementWidth(type);
-    if (width != 32 && width != 64) return false;
+    if (!inst->IsFloatingPointFoldingAllowed()) return false;
 
     const analysis::Constant* const_input1 = ConstInput(constants);
     if (!const_input1) return false;
     Instruction* other_inst = NonConstInput(context, constants[0], inst);
-    if (uses_float && !other_inst->IsFloatingPointFoldingAllowed())
-      return false;
+    if (!other_inst->IsFloatingPointFoldingAllowed()) return false;
 
     bool first_is_variable = constants[0] == nullptr;
-    if (other_inst->opcode() == SpvOpFNegate ||
-        other_inst->opcode() == SpvOpSNegate) {
+    if (other_inst->opcode() == SpvOpFNegate) {
       uint32_t neg_id = NegateConstant(const_mgr, const_input1);
 
       if (first_is_variable) {
@@ -1415,90 +1456,121 @@
   };
 }
 
-FoldingRule CompositeConstructFeedingExtract() {
-  return [](IRContext* context, Instruction* inst,
-            const std::vector<const analysis::Constant*>&) {
-    // If the input to an OpCompositeExtract is an OpCompositeConstruct,
-    // then we can simply use the appropriate element in the construction.
-    assert(inst->opcode() == SpvOpCompositeExtract &&
-           "Wrong opcode.  Should be OpCompositeExtract.");
-    analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
-    analysis::TypeManager* type_mgr = context->get_type_mgr();
+// Returns the number of elements that the |index|th in operand in |inst|
+// contributes to the result of |inst|.  |inst| must be an
+// OpCompositeConstructInstruction.
+uint32_t GetNumOfElementsContributedByOperand(IRContext* context,
+                                              const Instruction* inst,
+                                              uint32_t index) {
+  assert(inst->opcode() == SpvOpCompositeConstruct);
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  analysis::TypeManager* type_mgr = context->get_type_mgr();
 
-    // If there are no index operands, then this rule cannot do anything.
-    if (inst->NumInOperands() <= 1) {
-      return false;
-    }
+  analysis::Vector* result_type =
+      type_mgr->GetType(inst->type_id())->AsVector();
+  if (result_type == nullptr) {
+    // If the result of the OpCompositeConstruct is not a vector then every
+    // operands corresponds to a single element in the result.
+    return 1;
+  }
 
-    uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
-    Instruction* cinst = def_use_mgr->GetDef(cid);
+  // If the result type is a vector then the operands are either scalars or
+  // vectors. If it is a scalar, then it corresponds to a single element.  If it
+  // is a vector, then each element in the vector will be an element in the
+  // result.
+  uint32_t id = inst->GetSingleWordInOperand(index);
+  Instruction* def = def_use_mgr->GetDef(id);
+  analysis::Vector* type = type_mgr->GetType(def->type_id())->AsVector();
+  if (type == nullptr) {
+    return 1;
+  }
+  return type->element_count();
+}
 
-    if (cinst->opcode() != SpvOpCompositeConstruct) {
-      return false;
-    }
+// Returns the in-operands for an OpCompositeExtract instruction that are needed
+// to extract the |result_index|th element in the result of |inst| without using
+// the result of |inst|. Returns the empty vector if |result_index| is
+// out-of-bounds. |inst| must be an |OpCompositeConstruct| instruction.
+std::vector<Operand> GetExtractOperandsForElementOfCompositeConstruct(
+    IRContext* context, const Instruction* inst, uint32_t result_index) {
+  assert(inst->opcode() == SpvOpCompositeConstruct);
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  analysis::TypeManager* type_mgr = context->get_type_mgr();
 
-    std::vector<Operand> operands;
-    analysis::Type* composite_type = type_mgr->GetType(cinst->type_id());
-    if (composite_type->AsVector() == nullptr) {
-      // Get the element being extracted from the OpCompositeConstruct
-      // Since it is not a vector, it is simple to extract the single element.
-      uint32_t element_index = inst->GetSingleWordInOperand(1);
-      uint32_t element_id = cinst->GetSingleWordInOperand(element_index);
-      operands.push_back({SPV_OPERAND_TYPE_ID, {element_id}});
+  analysis::Type* result_type = type_mgr->GetType(inst->type_id());
+  if (result_type->AsVector() == nullptr) {
+    uint32_t id = inst->GetSingleWordInOperand(result_index);
+    return {Operand(SPV_OPERAND_TYPE_ID, {id})};
+  }
 
-      // Add the remaining indices for extraction.
-      for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
-        operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER,
-                            {inst->GetSingleWordInOperand(i)}});
+  // If the result type is a vector, then vector operands are concatenated.
+  uint32_t total_element_count = 0;
+  for (uint32_t idx = 0; idx < inst->NumInOperands(); ++idx) {
+    uint32_t element_count =
+        GetNumOfElementsContributedByOperand(context, inst, idx);
+    total_element_count += element_count;
+    if (result_index < total_element_count) {
+      std::vector<Operand> operands;
+      uint32_t id = inst->GetSingleWordInOperand(idx);
+      Instruction* operand_def = def_use_mgr->GetDef(id);
+      analysis::Type* operand_type = type_mgr->GetType(operand_def->type_id());
+
+      operands.push_back({SPV_OPERAND_TYPE_ID, {id}});
+      if (operand_type->AsVector()) {
+        uint32_t start_index_of_id = total_element_count - element_count;
+        uint32_t index_into_id = result_index - start_index_of_id;
+        operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index_into_id}});
       }
-
-    } else {
-      // With vectors we have to handle the case where it is concatenating
-      // vectors.
-      assert(inst->NumInOperands() == 2 &&
-             "Expecting a vector of scalar values.");
-
-      uint32_t element_index = inst->GetSingleWordInOperand(1);
-      for (uint32_t construct_index = 0;
-           construct_index < cinst->NumInOperands(); ++construct_index) {
-        uint32_t element_id = cinst->GetSingleWordInOperand(construct_index);
-        Instruction* element_def = def_use_mgr->GetDef(element_id);
-        analysis::Vector* element_type =
-            type_mgr->GetType(element_def->type_id())->AsVector();
-        if (element_type) {
-          uint32_t vector_size = element_type->element_count();
-          if (vector_size <= element_index) {
-            // The element we want comes after this vector.
-            element_index -= vector_size;
-          } else {
-            // We want an element of this vector.
-            operands.push_back({SPV_OPERAND_TYPE_ID, {element_id}});
-            operands.push_back(
-                {SPV_OPERAND_TYPE_LITERAL_INTEGER, {element_index}});
-            break;
-          }
-        } else {
-          if (element_index == 0) {
-            // This is a scalar, and we this is the element we are extracting.
-            operands.push_back({SPV_OPERAND_TYPE_ID, {element_id}});
-            break;
-          } else {
-            // Skip over this scalar value.
-            --element_index;
-          }
-        }
-      }
+      return operands;
     }
+  }
+  return {};
+}
 
+bool CompositeConstructFeedingExtract(
+    IRContext* context, Instruction* inst,
+    const std::vector<const analysis::Constant*>&) {
+  // If the input to an OpCompositeExtract is an OpCompositeConstruct,
+  // then we can simply use the appropriate element in the construction.
+  assert(inst->opcode() == SpvOpCompositeExtract &&
+         "Wrong opcode.  Should be OpCompositeExtract.");
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+
+  // If there are no index operands, then this rule cannot do anything.
+  if (inst->NumInOperands() <= 1) {
+    return false;
+  }
+
+  uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx);
+  Instruction* cinst = def_use_mgr->GetDef(cid);
+
+  if (cinst->opcode() != SpvOpCompositeConstruct) {
+    return false;
+  }
+
+  uint32_t index_into_result = inst->GetSingleWordInOperand(1);
+  std::vector<Operand> operands =
+      GetExtractOperandsForElementOfCompositeConstruct(context, cinst,
+                                                       index_into_result);
+
+  if (operands.empty()) {
+    return false;
+  }
+
+  // Add the remaining indices for extraction.
+  for (uint32_t i = 2; i < inst->NumInOperands(); ++i) {
+    operands.push_back(
+        {SPV_OPERAND_TYPE_LITERAL_INTEGER, {inst->GetSingleWordInOperand(i)}});
+  }
+
+  if (operands.size() == 1) {
     // If there were no extra indices, then we have the final object.  No need
-    // to extract even more.
-    if (operands.size() == 1) {
-      inst->SetOpcode(SpvOpCopyObject);
-    }
+    // to extract any more.
+    inst->SetOpcode(SpvOpCopyObject);
+  }
 
-    inst->SetInOperands(std::move(operands));
-    return true;
-  };
+  inst->SetInOperands(std::move(operands));
+  return true;
 }
 
 // If the OpCompositeConstruct is simply putting back together elements that
@@ -1796,6 +1868,35 @@
   };
 }
 
+FoldingRule BitCastScalarOrVector() {
+  return [](IRContext* context, Instruction* inst,
+            const std::vector<const analysis::Constant*>& constants) {
+    assert(inst->opcode() == SpvOpBitcast && constants.size() == 1);
+    if (constants[0] == nullptr) return false;
+
+    const analysis::Type* type =
+        context->get_type_mgr()->GetType(inst->type_id());
+    if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed())
+      return false;
+
+    analysis::ConstantManager* const_mgr = context->get_constant_mgr();
+    std::vector<uint32_t> words =
+        GetWordsFromNumericScalarOrVectorConstant(const_mgr, constants[0]);
+    if (words.size() == 0) return false;
+
+    const analysis::Constant* bitcasted_constant =
+        ConvertWordsToNumericScalarOrVectorConstant(const_mgr, words, type);
+    if (!bitcasted_constant) return false;
+
+    auto new_feeder_id =
+        const_mgr->GetDefiningInstruction(bitcasted_constant, inst->type_id())
+            ->result_id();
+    inst->SetOpcode(SpvOpCopyObject);
+    inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {new_feeder_id}}});
+    return true;
+  };
+}
+
 FoldingRule RedundantSelect() {
   // An OpSelect instruction where both values are the same or the condition is
   // constant can be replaced by one of the values
@@ -2423,10 +2524,12 @@
   // Note that the order in which rules are added to the list matters. If a rule
   // applies to the instruction, the rest of the rules will not be attempted.
   // Take that into consideration.
+  rules_[SpvOpBitcast].push_back(BitCastScalarOrVector());
+
   rules_[SpvOpCompositeConstruct].push_back(CompositeExtractFeedingConstruct);
 
   rules_[SpvOpCompositeExtract].push_back(InsertFeedingExtract());
-  rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract());
+  rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract);
   rules_[SpvOpCompositeExtract].push_back(VectorShuffleFeedingExtract());
   rules_[SpvOpCompositeExtract].push_back(FMixFeedingExtract());
 
@@ -2478,8 +2581,6 @@
 
   rules_[SpvOpPhi].push_back(RedundantPhi());
 
-  rules_[SpvOpSDiv].push_back(MergeDivNegateArithmetic());
-
   rules_[SpvOpSNegate].push_back(MergeNegateArithmetic());
   rules_[SpvOpSNegate].push_back(MergeNegateMulDivArithmetic());
   rules_[SpvOpSNegate].push_back(MergeNegateAddSubArithmetic());
@@ -2488,8 +2589,6 @@
 
   rules_[SpvOpStore].push_back(StoringUndef());
 
-  rules_[SpvOpUDiv].push_back(MergeDivNegateArithmetic());
-
   rules_[SpvOpVectorShuffle].push_back(VectorShuffleFeedingShuffle());
 
   rules_[SpvOpImageSampleImplicitLod].push_back(UpdateImageOperands());
diff --git a/source/opt/function.h b/source/opt/function.h
index 9e1c727..917bf58 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -177,6 +177,9 @@
   // debuggers.
   void Dump() const;
 
+  // Returns true is a function declaration and not a function definition.
+  bool IsDeclaration() { return begin() == end(); }
+
  private:
   // The OpFunction instruction that begins the definition of this function.
   std::unique_ptr<Instruction> def_inst_;
diff --git a/source/opt/graphics_robust_access_pass.cpp b/source/opt/graphics_robust_access_pass.cpp
index 46483e4..1b28f9b 100644
--- a/source/opt/graphics_robust_access_pass.cpp
+++ b/source/opt/graphics_robust_access_pass.cpp
@@ -272,10 +272,11 @@
 
   // Replaces one of the OpAccessChain index operands with a new value.
   // Updates def-use analysis.
-  auto replace_index = [&inst, def_use_mgr](uint32_t operand_index,
-                                            Instruction* new_value) {
+  auto replace_index = [this, &inst, def_use_mgr](uint32_t operand_index,
+                                                  Instruction* new_value) {
     inst.SetOperand(operand_index, {new_value->result_id()});
     def_use_mgr->AnalyzeInstUse(&inst);
+    module_status_.modified = true;
     return SPV_SUCCESS;
   };
 
diff --git a/source/opt/if_conversion.cpp b/source/opt/if_conversion.cpp
index 4284069..4920661 100644
--- a/source/opt/if_conversion.cpp
+++ b/source/opt/if_conversion.cpp
@@ -129,6 +129,7 @@
         Instruction* select = builder.AddSelect(phi->type_id(), condition,
                                                 true_value->result_id(),
                                                 false_value->result_id());
+        context()->get_def_use_mgr()->AnalyzeInstDefUse(select);
         select->UpdateDebugInfoFrom(phi);
         context()->ReplaceAllUsesWith(phi->result_id(), select->result_id());
         to_kill.push_back(phi);
diff --git a/source/opt/inline_exhaustive_pass.cpp b/source/opt/inline_exhaustive_pass.cpp
index f24f744..bef4501 100644
--- a/source/opt/inline_exhaustive_pass.cpp
+++ b/source/opt/inline_exhaustive_pass.cpp
@@ -65,7 +65,7 @@
     status = CombineStatus(status, InlineExhaustive(fp));
     return false;
   };
-  context()->ProcessEntryPointCallTree(pfn);
+  context()->ProcessReachableCallTree(pfn);
   return status;
 }
 
diff --git a/source/opt/inline_opaque_pass.cpp b/source/opt/inline_opaque_pass.cpp
index 6ccaf90..fe9c679 100644
--- a/source/opt/inline_opaque_pass.cpp
+++ b/source/opt/inline_opaque_pass.cpp
@@ -105,7 +105,7 @@
     status = CombineStatus(status, InlineOpaque(fp));
     return false;
   };
-  context()->ProcessEntryPointCallTree(pfn);
+  context()->ProcessReachableCallTree(pfn);
   return status;
 }
 
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index 8159ebf..2cc3125 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -92,7 +92,7 @@
                       {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}},
                        {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}}));
   if (line_inst != nullptr) {
-    newStore->dbg_line_insts().push_back(*line_inst);
+    newStore->AddDebugLine(line_inst);
   }
   newStore->SetDebugScope(dbg_scope);
   (*block_ptr)->AddInstruction(std::move(newStore));
@@ -106,7 +106,7 @@
       new Instruction(context(), SpvOpLoad, type_id, resultId,
                       {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}}));
   if (line_inst != nullptr) {
-    newLoad->dbg_line_insts().push_back(*line_inst);
+    newLoad->AddDebugLine(line_inst);
   }
   newLoad->SetDebugScope(dbg_scope);
   (*block_ptr)->AddInstruction(std::move(newLoad));
@@ -158,8 +158,8 @@
   auto callee_block_itr = calleeFn->begin();
   auto callee_var_itr = callee_block_itr->begin();
   while (callee_var_itr->opcode() == SpvOp::SpvOpVariable ||
-         callee_var_itr->GetOpenCL100DebugOpcode() ==
-             OpenCLDebugInfo100DebugDeclare) {
+         callee_var_itr->GetCommonDebugOpcode() ==
+             CommonDebugInfoDebugDeclare) {
     if (callee_var_itr->opcode() != SpvOp::SpvOpVariable) {
       ++callee_var_itr;
       continue;
@@ -300,8 +300,7 @@
     UptrVectorIterator<BasicBlock> callee_first_block_itr) {
   auto callee_itr = callee_first_block_itr->begin();
   while (callee_itr->opcode() == SpvOp::SpvOpVariable ||
-         callee_itr->GetOpenCL100DebugOpcode() ==
-             OpenCLDebugInfo100DebugDeclare) {
+         callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
     if (callee_itr->opcode() == SpvOp::SpvOpVariable &&
         callee_itr->NumInOperands() == 2) {
       assert(callee2caller.count(callee_itr->result_id()) &&
@@ -315,8 +314,7 @@
                context()->get_debug_info_mgr()->BuildDebugScope(
                    callee_itr->GetDebugScope(), inlined_at_ctx));
     }
-    if (callee_itr->GetOpenCL100DebugOpcode() ==
-        OpenCLDebugInfo100DebugDeclare) {
+    if (callee_itr->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
       InlineSingleInstruction(
           callee2caller, new_blk_ptr->get(), &*callee_itr,
           context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
@@ -405,6 +403,14 @@
       callee2caller, inlined_at_ctx, new_blk_ptr, callee_first_block);
 
   while (callee_inst_itr != callee_first_block->end()) {
+    // Don't inline function definition links, the calling function is not a
+    // definition.
+    if (callee_inst_itr->GetShader100DebugOpcode() ==
+        NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
+      ++callee_inst_itr;
+      continue;
+    }
+
     if (!InlineSingleInstruction(
             callee2caller, new_blk_ptr->get(), &*callee_inst_itr,
             context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
@@ -435,6 +441,11 @@
     auto tail_inst_itr = callee_block_itr->end();
     for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr;
          ++inst_itr) {
+      // Don't inline function definition links, the calling function is not a
+      // definition
+      if (inst_itr->GetShader100DebugOpcode() ==
+          NonSemanticShaderDebugInfo100DebugFunctionDefinition)
+        continue;
       if (!InlineSingleInstruction(
               callee2caller, new_blk_ptr.get(), &*inst_itr,
               context()->get_debug_info_mgr()->BuildDebugInlinedAtChain(
diff --git a/source/opt/inline_pass.h b/source/opt/inline_pass.h
index abe773a..9a5429b 100644
--- a/source/opt/inline_pass.h
+++ b/source/opt/inline_pass.h
@@ -37,7 +37,7 @@
   using cbb_ptr = const BasicBlock*;
 
  public:
-  virtual ~InlinePass() = default;
+  virtual ~InlinePass() override = default;
 
  protected:
   InlinePass();
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index 0085734..5607239 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -27,13 +27,16 @@
 static const int kSpvLoadPtrIdInIdx = 0;
 static const int kSpvAccessChainBaseIdInIdx = 0;
 static const int kSpvAccessChainIndex0IdInIdx = 1;
+static const int kSpvTypeArrayTypeIdInIdx = 0;
 static const int kSpvTypeArrayLengthIdInIdx = 1;
 static const int kSpvConstantValueInIdx = 0;
 static const int kSpvVariableStorageClassInIdx = 0;
+static const int kSpvTypePtrTypeIdInIdx = 1;
 static const int kSpvTypeImageDim = 1;
 static const int kSpvTypeImageDepth = 2;
 static const int kSpvTypeImageArrayed = 3;
 static const int kSpvTypeImageMS = 4;
+static const int kSpvTypeImageSampled = 5;
 }  // anonymous namespace
 
 // Avoid unused variable warning/error on Linux
@@ -206,13 +209,40 @@
         var_inst->GetSingleWordInOperand(kSpvVariableStorageClassInIdx);
     switch (storage_class) {
       case SpvStorageClassUniform:
-      case SpvStorageClassUniformConstant:
       case SpvStorageClassStorageBuffer:
         break;
       default:
         return false;
         break;
     }
+    // Check for deprecated storage block form
+    if (storage_class == SpvStorageClassUniform) {
+      uint32_t var_ty_id = var_inst->type_id();
+      Instruction* var_ty_inst = get_def_use_mgr()->GetDef(var_ty_id);
+      uint32_t ptr_ty_id =
+          var_ty_inst->GetSingleWordInOperand(kSpvTypePtrTypeIdInIdx);
+      Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id);
+      SpvOp ptr_ty_op = ptr_ty_inst->opcode();
+      uint32_t block_ty_id =
+          (ptr_ty_op == SpvOpTypeArray || ptr_ty_op == SpvOpTypeRuntimeArray)
+              ? ptr_ty_inst->GetSingleWordInOperand(kSpvTypeArrayTypeIdInIdx)
+              : ptr_ty_id;
+      assert(get_def_use_mgr()->GetDef(block_ty_id)->opcode() ==
+                 SpvOpTypeStruct &&
+             "unexpected block type");
+      bool block_found = get_decoration_mgr()->FindDecoration(
+          block_ty_id, SpvDecorationBlock,
+          [](const Instruction&) { return true; });
+      if (!block_found) {
+        // If block decoration not found, verify deprecated form of SSBO
+        bool buffer_block_found = get_decoration_mgr()->FindDecoration(
+            block_ty_id, SpvDecorationBufferBlock,
+            [](const Instruction&) { return true; });
+        USE_ASSERT(buffer_block_found && "block decoration not found");
+        storage_class = SpvStorageClassStorageBuffer;
+      }
+    }
+    ref->strg_class = storage_class;
     Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
     switch (desc_type_inst->opcode()) {
       case SpvOpTypeArray:
@@ -665,8 +695,10 @@
   // for the referenced value.
   Instruction* ult_inst =
       builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref_id, init_id);
-  uint32_t error =
-      init_check ? kInstErrorBindlessUninit : kInstErrorBindlessBuffOOB;
+  uint32_t error = init_check ? kInstErrorBindlessUninit
+                              : (ref.strg_class == SpvStorageClassUniform
+                                     ? kInstErrorBuffOOBUniform
+                                     : kInstErrorBuffOOBStorage);
   uint32_t error_id = builder.GetUintConstantId(error);
   GenCheckCode(ult_inst->result_id(), error_id, init_check ? 0 : ref_id,
                init_check ? builder.GetUintConstantId(0u) : init_id, stage_idx,
@@ -732,7 +764,11 @@
   // for the referenced value.
   Instruction* ult_inst =
       builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, coord_id, size_id);
-  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBuffOOB);
+  uint32_t error =
+      (image_ty_inst->GetSingleWordInOperand(kSpvTypeImageSampled) == 2)
+          ? kInstErrorBuffOOBStorageTexel
+          : kInstErrorBuffOOBUniformTexel;
+  uint32_t error_id = builder.GetUintConstantId(error);
   GenCheckCode(ult_inst->result_id(), error_id, coord_id, size_id, stage_idx,
                &ref, new_blocks);
   // Move original block's remaining code into remainder/merge block and add
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index a7dff75..cd96180 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -130,6 +130,7 @@
     uint32_t ptr_id;
     uint32_t var_id;
     uint32_t desc_idx_id;
+    uint32_t strg_class;
     Instruction* ref_inst;
   } RefAnalysis;
 
diff --git a/source/opt/inst_buff_addr_check_pass.cpp b/source/opt/inst_buff_addr_check_pass.cpp
index 06acc7e..e2336d3 100644
--- a/source/opt/inst_buff_addr_check_pass.cpp
+++ b/source/opt/inst_buff_addr_check_pass.cpp
@@ -130,13 +130,48 @@
   context()->KillInst(ref_inst);
 }
 
+uint32_t InstBuffAddrCheckPass::GetTypeAlignment(uint32_t type_id) {
+  Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+  switch (type_inst->opcode()) {
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt:
+    case SpvOpTypeVector:
+      return GetTypeLength(type_id);
+    case SpvOpTypeMatrix:
+      return GetTypeAlignment(type_inst->GetSingleWordInOperand(0));
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      return GetTypeAlignment(type_inst->GetSingleWordInOperand(0));
+    case SpvOpTypeStruct: {
+      uint32_t max = 0;
+      type_inst->ForEachInId([&max, this](const uint32_t* iid) {
+        uint32_t alignment = GetTypeAlignment(*iid);
+        max = (alignment > max) ? alignment : max;
+      });
+      return max;
+    }
+    case SpvOpTypePointer:
+      assert(type_inst->GetSingleWordInOperand(0) ==
+                 SpvStorageClassPhysicalStorageBufferEXT &&
+             "unexpected pointer type");
+      return 8u;
+    default:
+      assert(false && "unexpected type");
+      return 0;
+  }
+}
+
 uint32_t InstBuffAddrCheckPass::GetTypeLength(uint32_t type_id) {
   Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
   switch (type_inst->opcode()) {
     case SpvOpTypeFloat:
     case SpvOpTypeInt:
       return type_inst->GetSingleWordInOperand(0) / 8u;
-    case SpvOpTypeVector:
+    case SpvOpTypeVector: {
+      uint32_t raw_cnt = type_inst->GetSingleWordInOperand(1);
+      uint32_t adj_cnt = (raw_cnt == 3u) ? 4u : raw_cnt;
+      return adj_cnt * GetTypeLength(type_inst->GetSingleWordInOperand(0));
+    }
     case SpvOpTypeMatrix:
       return type_inst->GetSingleWordInOperand(1) *
              GetTypeLength(type_inst->GetSingleWordInOperand(0));
@@ -145,8 +180,29 @@
                  SpvStorageClassPhysicalStorageBufferEXT &&
              "unexpected pointer type");
       return 8u;
+    case SpvOpTypeArray: {
+      uint32_t const_id = type_inst->GetSingleWordInOperand(1);
+      Instruction* const_inst = get_def_use_mgr()->GetDef(const_id);
+      uint32_t cnt = const_inst->GetSingleWordInOperand(0);
+      return cnt * GetTypeLength(type_inst->GetSingleWordInOperand(0));
+    }
+    case SpvOpTypeStruct: {
+      uint32_t len = 0;
+      type_inst->ForEachInId([&len, this](const uint32_t* iid) {
+        // Align struct length
+        uint32_t alignment = GetTypeAlignment(*iid);
+        uint32_t mod = len % alignment;
+        uint32_t diff = (mod != 0) ? alignment - mod : 0;
+        len += diff;
+        // Increment struct length by component length
+        uint32_t comp_len = GetTypeLength(*iid);
+        len += comp_len;
+      });
+      return len;
+    }
+    case SpvOpTypeRuntimeArray:
     default:
-      assert(false && "unexpected buffer reference type");
+      assert(false && "unexpected type");
       return 0;
   }
 }
diff --git a/source/opt/inst_buff_addr_check_pass.h b/source/opt/inst_buff_addr_check_pass.h
index ec7bb68..a823223 100644
--- a/source/opt/inst_buff_addr_check_pass.h
+++ b/source/opt/inst_buff_addr_check_pass.h
@@ -28,7 +28,9 @@
 // external design of this class may change as the layer evolves.
 class InstBuffAddrCheckPass : public InstrumentPass {
  public:
-  // Preferred interface
+  // For test harness only
+  InstBuffAddrCheckPass() : InstrumentPass(7, 23, kInstValidationIdBuffAddr) {}
+  // For all other interfaces
   InstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id)
       : InstrumentPass(desc_set, shader_id, kInstValidationIdBuffAddr) {}
 
@@ -40,8 +42,12 @@
   const char* name() const override { return "inst-bindless-check-pass"; }
 
  private:
-  // Return byte length of type |type_id|. Must be int, float, vector, matrix
-  // or physical pointer.
+  // Return byte alignment of type |type_id|. Must be int, float, vector,
+  // matrix, struct, array or physical pointer. Uses std430 alignment.
+  uint32_t GetTypeAlignment(uint32_t type_id);
+
+  // Return byte length of type |type_id|. Must be int, float, vector, matrix,
+  // struct, array or physical pointer. Uses std430 alignment and sizes.
   uint32_t GetTypeLength(uint32_t type_id);
 
   // Add |type_id| param to |input_func| and add id to |param_vec|.
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 1054a20..2461e41 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -30,9 +30,11 @@
 const uint32_t kTypeImageDimIndex = 1;
 const uint32_t kLoadBaseIndex = 0;
 const uint32_t kPointerTypeStorageClassIndex = 0;
+const uint32_t kVariableStorageClassIndex = 0;
 const uint32_t kTypeImageSampledIndex = 5;
 
-// Constants for OpenCL.DebugInfo.100 extension instructions.
+// Constants for OpenCL.DebugInfo.100 / NonSemantic.Shader.DebugInfo.100
+// extension instructions.
 const uint32_t kExtInstSetIdInIdx = 0;
 const uint32_t kExtInstInstructionInIdx = 1;
 const uint32_t kDebugScopeNumWords = 7;
@@ -64,15 +66,14 @@
 
 Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
                          std::vector<Instruction>&& dbg_line)
-    : context_(c),
+    : utils::IntrusiveNodeBase<Instruction>(),
+      context_(c),
       opcode_(static_cast<SpvOp>(inst.opcode)),
       has_type_id_(inst.type_id != 0),
       has_result_id_(inst.result_id != 0),
       unique_id_(c->TakeNextUniqueId()),
       dbg_line_insts_(std::move(dbg_line)),
       dbg_scope_(kNoDebugScope, kNoInlinedAt) {
-  assert((!IsDebugLineInst(opcode_) || dbg_line.empty()) &&
-         "Op(No)Line attaching to Op(No)Line found");
   for (uint32_t i = 0; i < inst.num_operands; ++i) {
     const auto& current_payload = inst.operands[i];
     std::vector<uint32_t> words(
@@ -80,11 +81,14 @@
         inst.words + current_payload.offset + current_payload.num_words);
     operands_.emplace_back(current_payload.type, std::move(words));
   }
+  assert((!IsLineInst() || dbg_line.empty()) &&
+         "Op(No)Line attaching to Op(No)Line found");
 }
 
 Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst,
                          const DebugScope& dbg_scope)
-    : context_(c),
+    : utils::IntrusiveNodeBase<Instruction>(),
+      context_(c),
       opcode_(static_cast<SpvOp>(inst.opcode)),
       has_type_id_(inst.type_id != 0),
       has_result_id_(inst.result_id != 0),
@@ -122,6 +126,7 @@
 
 Instruction::Instruction(Instruction&& that)
     : utils::IntrusiveNodeBase<Instruction>(),
+      context_(that.context_),
       opcode_(that.opcode_),
       has_type_id_(that.has_type_id_),
       has_result_id_(that.has_result_id_),
@@ -135,6 +140,7 @@
 }
 
 Instruction& Instruction::operator=(Instruction&& that) {
+  context_ = that.context_;
   opcode_ = that.opcode_;
   has_type_id_ = that.has_type_id_;
   has_result_id_ = that.has_result_id_;
@@ -153,6 +159,10 @@
   clone->unique_id_ = c->TakeNextUniqueId();
   clone->operands_ = operands_;
   clone->dbg_line_insts_ = dbg_line_insts_;
+  for (auto& i : clone->dbg_line_insts_) {
+    i.unique_id_ = c->TakeNextUniqueId();
+    if (i.IsDebugLineInst()) i.SetResultId(c->TakeNextId());
+  }
   clone->dbg_scope_ = dbg_scope_;
   return clone;
 }
@@ -394,6 +404,21 @@
   return false;
 }
 
+bool Instruction::IsVulkanStorageBufferVariable() const {
+  if (opcode() != SpvOpVariable) {
+    return false;
+  }
+
+  uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex);
+  if (storage_class == SpvStorageClassStorageBuffer ||
+      storage_class == SpvStorageClassUniform) {
+    Instruction* var_type = context()->get_def_use_mgr()->GetDef(type_id());
+    return var_type != nullptr && var_type->IsVulkanStorageBuffer();
+  }
+
+  return false;
+}
+
 bool Instruction::IsVulkanUniformBuffer() const {
   if (opcode() != SpvOpTypePointer) {
     return false;
@@ -506,7 +531,7 @@
   for (auto& i : dbg_line_insts_) {
     i.dbg_scope_.SetLexicalScope(scope);
   }
-  if (!IsDebugLineInst(opcode()) &&
+  if (!IsLineInst() &&
       context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) {
     context()->get_debug_info_mgr()->AnalyzeDebugInst(this);
   }
@@ -517,24 +542,61 @@
   for (auto& i : dbg_line_insts_) {
     i.dbg_scope_.SetInlinedAt(new_inlined_at);
   }
-  if (!IsDebugLineInst(opcode()) &&
+  if (!IsLineInst() &&
       context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) {
     context()->get_debug_info_mgr()->AnalyzeDebugInst(this);
   }
 }
 
+void Instruction::ClearDbgLineInsts() {
+  if (context()->AreAnalysesValid(IRContext::kAnalysisDefUse)) {
+    auto def_use_mgr = context()->get_def_use_mgr();
+    for (auto& l_inst : dbg_line_insts_) def_use_mgr->ClearInst(&l_inst);
+  }
+  clear_dbg_line_insts();
+}
+
 void Instruction::UpdateDebugInfoFrom(const Instruction* from) {
   if (from == nullptr) return;
-  clear_dbg_line_insts();
+  ClearDbgLineInsts();
   if (!from->dbg_line_insts().empty())
-    dbg_line_insts().push_back(from->dbg_line_insts().back());
+    AddDebugLine(&from->dbg_line_insts().back());
   SetDebugScope(from->GetDebugScope());
-  if (!IsDebugLineInst(opcode()) &&
+  if (!IsLineInst() &&
       context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) {
     context()->get_debug_info_mgr()->AnalyzeDebugInst(this);
   }
 }
 
+void Instruction::AddDebugLine(const Instruction* inst) {
+  dbg_line_insts_.push_back(*inst);
+  dbg_line_insts_.back().unique_id_ = context()->TakeNextUniqueId();
+  if (inst->IsDebugLineInst())
+    dbg_line_insts_.back().SetResultId(context_->TakeNextId());
+  if (context()->AreAnalysesValid(IRContext::kAnalysisDefUse))
+    context()->get_def_use_mgr()->AnalyzeInstDefUse(&dbg_line_insts_.back());
+}
+
+bool Instruction::IsDebugLineInst() const {
+  NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode();
+  return ((ext_opt == NonSemanticShaderDebugInfo100DebugLine) ||
+          (ext_opt == NonSemanticShaderDebugInfo100DebugNoLine));
+}
+
+bool Instruction::IsLineInst() const { return IsLine() || IsNoLine(); }
+
+bool Instruction::IsLine() const {
+  if (opcode() == SpvOpLine) return true;
+  NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode();
+  return ext_opt == NonSemanticShaderDebugInfo100DebugLine;
+}
+
+bool Instruction::IsNoLine() const {
+  if (opcode() == SpvOpNoLine) return true;
+  NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode();
+  return ext_opt == NonSemanticShaderDebugInfo100DebugNoLine;
+}
+
 Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& inst) {
   inst.get()->InsertBefore(this);
   return inst.release();
@@ -618,6 +680,49 @@
       GetSingleWordInOperand(kExtInstInstructionInIdx));
 }
 
+NonSemanticShaderDebugInfo100Instructions Instruction::GetShader100DebugOpcode()
+    const {
+  if (opcode() != SpvOpExtInst) {
+    return NonSemanticShaderDebugInfo100InstructionsMax;
+  }
+
+  if (!context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo()) {
+    return NonSemanticShaderDebugInfo100InstructionsMax;
+  }
+
+  if (GetSingleWordInOperand(kExtInstSetIdInIdx) !=
+      context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo()) {
+    return NonSemanticShaderDebugInfo100InstructionsMax;
+  }
+
+  return NonSemanticShaderDebugInfo100Instructions(
+      GetSingleWordInOperand(kExtInstInstructionInIdx));
+}
+
+CommonDebugInfoInstructions Instruction::GetCommonDebugOpcode() const {
+  if (opcode() != SpvOpExtInst) {
+    return CommonDebugInfoInstructionsMax;
+  }
+
+  const uint32_t opencl_set_id =
+      context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo();
+  const uint32_t shader_set_id =
+      context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo();
+
+  if (!opencl_set_id && !shader_set_id) {
+    return CommonDebugInfoInstructionsMax;
+  }
+
+  const uint32_t used_set_id = GetSingleWordInOperand(kExtInstSetIdInIdx);
+
+  if (used_set_id != opencl_set_id && used_set_id != shader_set_id) {
+    return CommonDebugInfoInstructionsMax;
+  }
+
+  return CommonDebugInfoInstructions(
+      GetSingleWordInOperand(kExtInstInstructionInIdx));
+}
+
 bool Instruction::IsValidBaseImage() const {
   uint32_t tid = type_id();
   if (tid == 0) {
@@ -940,10 +1045,10 @@
                           uint32_t ext_set,
                           std::vector<uint32_t>* binary) const {
   uint32_t num_words = kDebugScopeNumWords;
-  OpenCLDebugInfo100Instructions dbg_opcode = OpenCLDebugInfo100DebugScope;
+  CommonDebugInfoInstructions dbg_opcode = CommonDebugInfoDebugScope;
   if (GetLexicalScope() == kNoDebugScope) {
     num_words = kDebugNoScopeNumWords;
-    dbg_opcode = OpenCLDebugInfo100DebugNoScope;
+    dbg_opcode = CommonDebugInfoDebugNoScope;
   } else if (GetInlinedAt() == kNoInlinedAt) {
     num_words = kDebugScopeNumWordsWithoutInlinedAt;
   }
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index 4743221..ce568f6 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -22,7 +22,9 @@
 #include <utility>
 #include <vector>
 
+#include "NonSemanticShaderDebugInfo100.h"
 #include "OpenCLDebugInfo100.h"
+#include "source/common_debug_info.h"
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/latest_version_spirv_header.h"
 #include "source/opcode.h"
@@ -209,7 +211,7 @@
   Instruction(Instruction&&);
   Instruction& operator=(Instruction&&);
 
-  virtual ~Instruction() = default;
+  ~Instruction() override = default;
 
   // Returns a newly allocated instruction that has the same operands, result,
   // and type as |this|.  The new instruction is not linked into any list.
@@ -250,11 +252,6 @@
   // Clear line-related debug instructions attached to this instruction.
   void clear_dbg_line_insts() { dbg_line_insts_.clear(); }
 
-  // Set line-related debug instructions.
-  void set_dbg_line_insts(const std::vector<Instruction>& lines) {
-    dbg_line_insts_ = lines;
-  }
-
   // Same semantics as in the base class except the list the InstructionList
   // containing |pos| will now assume ownership of |this|.
   // inline void MoveBefore(Instruction* pos);
@@ -304,8 +301,21 @@
   // Sets DebugScope.
   inline void SetDebugScope(const DebugScope& scope);
   inline const DebugScope& GetDebugScope() const { return dbg_scope_; }
+  // Add debug line inst. Renew result id if Debug[No]Line
+  void AddDebugLine(const Instruction* inst);
   // Updates DebugInlinedAt of DebugScope and OpLine.
   void UpdateDebugInlinedAt(uint32_t new_inlined_at);
+  // Clear line-related debug instructions attached to this instruction
+  // along with def-use entries.
+  void ClearDbgLineInsts();
+  // Return true if Shader100:Debug[No]Line
+  bool IsDebugLineInst() const;
+  // Return true if Op[No]Line or Shader100:Debug[No]Line
+  bool IsLineInst() const;
+  // Return true if OpLine or Shader100:DebugLine
+  bool IsLine() const;
+  // Return true if OpNoLine or Shader100:DebugNoLine
+  bool IsNoLine() const;
   inline uint32_t GetDebugInlinedAt() const {
     return dbg_scope_.GetInlinedAt();
   }
@@ -454,6 +464,10 @@
   // storage buffer.
   bool IsVulkanStorageBuffer() const;
 
+  // Returns true if the instruction defines a variable in StorageBuffer or
+  // Uniform storage class with a pointer type that points to a storage buffer.
+  bool IsVulkanStorageBufferVariable() const;
+
   // Returns true if the instruction defines a pointer type that points to a
   // uniform buffer.
   bool IsVulkanUniformBuffer() const;
@@ -550,11 +564,30 @@
   // OpenCLDebugInfo100InstructionsMax.
   OpenCLDebugInfo100Instructions GetOpenCL100DebugOpcode() const;
 
+  // Returns debug opcode of an NonSemantic.Shader.DebugInfo.100 instruction. If
+  // it is not an NonSemantic.Shader.DebugInfo.100 instruction, just return
+  // NonSemanticShaderDebugInfo100InstructionsMax.
+  NonSemanticShaderDebugInfo100Instructions GetShader100DebugOpcode() const;
+
+  // Returns debug opcode of an OpenCL.100.DebugInfo or
+  // NonSemantic.Shader.DebugInfo.100 instruction. Since these overlap, we
+  // return the OpenCLDebugInfo code
+  CommonDebugInfoInstructions GetCommonDebugOpcode() const;
+
   // Returns true if it is an OpenCL.DebugInfo.100 instruction.
   bool IsOpenCL100DebugInstr() const {
     return GetOpenCL100DebugOpcode() != OpenCLDebugInfo100InstructionsMax;
   }
 
+  // Returns true if it is an NonSemantic.Shader.DebugInfo.100 instruction.
+  bool IsShader100DebugInstr() const {
+    return GetShader100DebugOpcode() !=
+           NonSemanticShaderDebugInfo100InstructionsMax;
+  }
+  bool IsCommonDebugInstr() const {
+    return GetCommonDebugOpcode() != CommonDebugInfoInstructionsMax;
+  }
+
   // Returns true if this instructions a non-semantic instruction.
   bool IsNonSemanticInstruction() const;
 
@@ -588,9 +621,9 @@
   uint32_t unique_id_;  // Unique instruction id
   // All logical operands, including result type id and result id.
   OperandList operands_;
-  // Opline and OpNoLine instructions preceding this instruction. Note that for
-  // Instructions representing OpLine or OpNonLine itself, this field should be
-  // empty.
+  // Op[No]Line or Debug[No]Line instructions preceding this instruction. Note
+  // that for Instructions representing Op[No]Line or Debug[No]Line themselves,
+  // this field should be empty.
   std::vector<Instruction> dbg_line_insts_;
 
   // DebugScope that wraps this instruction.
diff --git a/source/opt/instruction_list.h b/source/opt/instruction_list.h
index 417cbd7..b3e4274 100644
--- a/source/opt/instruction_list.h
+++ b/source/opt/instruction_list.h
@@ -53,7 +53,7 @@
   }
 
   // Destroy this list and any instructions in the list.
-  inline virtual ~InstructionList();
+  inline ~InstructionList() override;
 
   class iterator : public utils::IntrusiveList<Instruction>::iterator {
    public:
diff --git a/source/opt/interp_fixup_pass.cpp b/source/opt/interp_fixup_pass.cpp
new file mode 100644
index 0000000..ad29e6a
--- /dev/null
+++ b/source/opt/interp_fixup_pass.cpp
@@ -0,0 +1,131 @@
+// Copyright (c) 2021 The Khronos Group Inc.
+// Copyright (c) 2021 Valve Corporation
+// Copyright (c) 2021 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/interp_fixup_pass.h"
+
+#include <set>
+#include <string>
+
+#include "ir_builder.h"
+#include "source/opt/ir_context.h"
+#include "type_manager.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace {
+
+// Input Operand Indices
+static const int kSpvVariableStorageClassInIdx = 0;
+
+// Avoid unused variable warning/error on Linux
+#ifndef NDEBUG
+#define USE_ASSERT(x) assert(x)
+#else
+#define USE_ASSERT(x) ((void)(x))
+#endif
+
+// Folding rule function which attempts to replace |op(OpLoad(a),...)|
+// by |op(a,...)|, where |op| is one of the GLSLstd450 InterpolateAt*
+// instructions. Returns true if replaced, false otherwise.
+bool ReplaceInternalInterpolate(IRContext* ctx, Instruction* inst,
+                                const std::vector<const analysis::Constant*>&) {
+  uint32_t glsl450_ext_inst_id =
+      ctx->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
+  assert(glsl450_ext_inst_id != 0);
+
+  uint32_t ext_opcode = inst->GetSingleWordInOperand(1);
+
+  uint32_t op1_id = inst->GetSingleWordInOperand(2);
+
+  Instruction* load_inst = ctx->get_def_use_mgr()->GetDef(op1_id);
+  if (load_inst->opcode() != SpvOpLoad) return false;
+
+  Instruction* base_inst = load_inst->GetBaseAddress();
+  USE_ASSERT(base_inst->opcode() == SpvOpVariable &&
+             base_inst->GetSingleWordInOperand(kSpvVariableStorageClassInIdx) ==
+                 SpvStorageClassInput &&
+             "unexpected interpolant in InterpolateAt*");
+
+  uint32_t ptr_id = load_inst->GetSingleWordInOperand(0);
+  uint32_t op2_id = (ext_opcode != GLSLstd450InterpolateAtCentroid)
+                        ? inst->GetSingleWordInOperand(3)
+                        : 0;
+
+  Instruction::OperandList new_operands;
+  new_operands.push_back({SPV_OPERAND_TYPE_ID, {glsl450_ext_inst_id}});
+  new_operands.push_back(
+      {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {ext_opcode}});
+  new_operands.push_back({SPV_OPERAND_TYPE_ID, {ptr_id}});
+  if (op2_id != 0) new_operands.push_back({SPV_OPERAND_TYPE_ID, {op2_id}});
+
+  inst->SetInOperands(std::move(new_operands));
+  ctx->UpdateDefUse(inst);
+  return true;
+}
+
+class InterpFoldingRules : public FoldingRules {
+ public:
+  explicit InterpFoldingRules(IRContext* ctx) : FoldingRules(ctx) {}
+
+ protected:
+  virtual void AddFoldingRules() override {
+    uint32_t extension_id =
+        context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450();
+
+    if (extension_id != 0) {
+      ext_rules_[{extension_id, GLSLstd450InterpolateAtCentroid}].push_back(
+          ReplaceInternalInterpolate);
+      ext_rules_[{extension_id, GLSLstd450InterpolateAtSample}].push_back(
+          ReplaceInternalInterpolate);
+      ext_rules_[{extension_id, GLSLstd450InterpolateAtOffset}].push_back(
+          ReplaceInternalInterpolate);
+    }
+  }
+};
+
+class InterpConstFoldingRules : public ConstantFoldingRules {
+ public:
+  InterpConstFoldingRules(IRContext* ctx) : ConstantFoldingRules(ctx) {}
+
+ protected:
+  virtual void AddFoldingRules() override {}
+};
+
+}  // namespace
+
+Pass::Status InterpFixupPass::Process() {
+  bool changed = false;
+
+  // Traverse the body of the functions to replace instructions that require
+  // the extensions.
+  InstructionFolder folder(
+      context(),
+      std::unique_ptr<InterpFoldingRules>(new InterpFoldingRules(context())),
+      MakeUnique<InterpConstFoldingRules>(context()));
+  for (Function& func : *get_module()) {
+    func.ForEachInst([&changed, &folder](Instruction* inst) {
+      if (folder.FoldInstruction(inst)) {
+        changed = true;
+      }
+    });
+  }
+
+  return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/interp_fixup_pass.h b/source/opt/interp_fixup_pass.h
new file mode 100644
index 0000000..e112b65
--- /dev/null
+++ b/source/opt/interp_fixup_pass.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2021 The Khronos Group Inc.
+// Copyright (c) 2021 Valve Corporation
+// Copyright (c) 2021 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_INTERP_FIXUP_H
+#define SOURCE_OPT_INTERP_FIXUP_H
+
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "source/opt/pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Replaces overloaded internal form for GLSLstd450Interpolate* instructions
+// with external form. Specifically, removes OpLoad from the first argument
+// and replaces it with the pointer for the OpLoad. glslang generates the
+// internal form. This pass is called as part of glslang HLSL legalization.
+class InterpFixupPass : public Pass {
+ public:
+  const char* name() const override { return "interp-fixup"; }
+  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_INTERP_FIXUP_H
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index fe5feff..4433cf0 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -359,8 +359,9 @@
     return AddInstruction(std::move(select));
   }
 
-  // Adds a signed int32 constant to the binary.
-  // The |value| parameter is the constant value to be added.
+  // Returns a pointer to the definition of a signed 32-bit integer constant
+  // with the given value. Returns |nullptr| if the constant does not exist and
+  // cannot be created.
   Instruction* GetSintConstant(int32_t value) {
     return GetIntConstant<int32_t>(value, true);
   }
@@ -381,21 +382,24 @@
                         GetContext()->TakeNextId(), ops));
     return AddInstruction(std::move(construct));
   }
-  // Adds an unsigned int32 constant to the binary.
-  // The |value| parameter is the constant value to be added.
+
+  // Returns a pointer to the definition of an unsigned 32-bit integer constant
+  // with the given value. Returns |nullptr| if the constant does not exist and
+  // cannot be created.
   Instruction* GetUintConstant(uint32_t value) {
     return GetIntConstant<uint32_t>(value, false);
   }
 
   uint32_t GetUintConstantId(uint32_t value) {
     Instruction* uint_inst = GetUintConstant(value);
-    return uint_inst->result_id();
+    return (uint_inst != nullptr ? uint_inst->result_id() : 0);
   }
 
   // Adds either a signed or unsigned 32 bit integer constant to the binary
-  // depedning on the |sign|. If |sign| is true then the value is added as a
+  // depending on the |sign|. If |sign| is true then the value is added as a
   // signed constant otherwise as an unsigned constant. If |sign| is false the
-  // value must not be a negative number.
+  // value must not be a negative number.  Returns false if the constant does
+  // not exists and could be be created.
   template <typename T>
   Instruction* GetIntConstant(T value, bool sign) {
     // Assert that we are not trying to store a negative number in an unsigned
@@ -411,6 +415,10 @@
     uint32_t type_id =
         GetContext()->get_type_mgr()->GetTypeInstruction(&int_type);
 
+    if (type_id == 0) {
+      return nullptr;
+    }
+
     // Get the memory managed type so that it is safe to be stored by
     // GetConstant.
     analysis::Type* rebuilt_type =
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index 82107b5..612a831 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -30,7 +30,8 @@
 static const int kEntryPointInterfaceInIdx = 3;
 static const int kEntryPointFunctionIdInIdx = 1;
 
-// Constants for OpenCL.DebugInfo.100 extension instructions.
+// Constants for OpenCL.DebugInfo.100 / NonSemantic.Shader.DebugInfo.100
+// extension instructions.
 static const uint32_t kDebugFunctionOperandFunctionIndex = 13;
 static const uint32_t kDebugGlobalVariableOperandVariableIndex = 11;
 
@@ -170,7 +171,9 @@
   KillOperandFromDebugInstructions(inst);
 
   if (AreAnalysesValid(kAnalysisDefUse)) {
-    get_def_use_mgr()->ClearInst(inst);
+    analysis::DefUseManager* def_use_mgr = get_def_use_mgr();
+    def_use_mgr->ClearInst(inst);
+    for (auto& l_inst : inst->dbg_line_insts()) def_use_mgr->ClearInst(&l_inst);
   }
   if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) {
     instr_to_block_.erase(inst);
@@ -214,10 +217,12 @@
   return next_instruction;
 }
 
-void IRContext::KillNonSemanticInfo(Instruction* inst) {
+void IRContext::CollectNonSemanticTree(
+    Instruction* inst, std::unordered_set<Instruction*>* to_kill) {
   if (!inst->HasResultId()) return;
+  // Debug[No]Line result id is not used, so we are done
+  if (inst->IsDebugLineInst()) return;
   std::vector<Instruction*> work_list;
-  std::vector<Instruction*> to_kill;
   std::unordered_set<Instruction*> seen;
   work_list.push_back(inst);
 
@@ -225,17 +230,13 @@
     auto* i = work_list.back();
     work_list.pop_back();
     get_def_use_mgr()->ForEachUser(
-        i, [&work_list, &to_kill, &seen](Instruction* user) {
+        i, [&work_list, to_kill, &seen](Instruction* user) {
           if (user->IsNonSemanticInstruction() && seen.insert(user).second) {
             work_list.push_back(user);
-            to_kill.push_back(user);
+            to_kill->insert(user);
           }
         });
   }
-
-  for (auto* dead : to_kill) {
-    KillInst(dead);
-  }
 }
 
 bool IRContext::KillDef(uint32_t id) {
@@ -441,8 +442,7 @@
   if (opcode == SpvOpVariable || IsConstantInst(opcode)) {
     for (auto it = module()->ext_inst_debuginfo_begin();
          it != module()->ext_inst_debuginfo_end(); ++it) {
-      if (it->GetOpenCL100DebugOpcode() !=
-          OpenCLDebugInfo100DebugGlobalVariable)
+      if (it->GetCommonDebugOpcode() != CommonDebugInfoDebugGlobalVariable)
         continue;
       auto& operand = it->GetOperand(kDebugGlobalVariableOperandVariableIndex);
       if (operand.words[0] == id) {
@@ -934,7 +934,7 @@
   while (line_inst != nullptr) {  // Stop at the beginning of the basic block.
     if (!line_inst->dbg_line_insts().empty()) {
       line_inst = &line_inst->dbg_line_insts().back();
-      if (line_inst->opcode() == SpvOpNoLine) {
+      if (line_inst->IsNoLine()) {
         line_inst = nullptr;
       }
       break;
@@ -1038,5 +1038,11 @@
 
   return true;
 }
+
+bool IRContext::IsReachable(const opt::BasicBlock& bb) {
+  auto enclosing_function = bb.GetParent();
+  return GetDominatorAnalysis(enclosing_function)
+      ->Dominates(enclosing_function->entry().get(), &bb);
+}
 }  // namespace opt
 }  // namespace spvtools
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 5aa25ac..6585347 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -98,6 +98,7 @@
         module_(new Module()),
         consumer_(std::move(c)),
         def_use_mgr_(nullptr),
+        feature_mgr_(nullptr),
         valid_analyses_(kAnalysisNone),
         constant_mgr_(nullptr),
         type_mgr_(nullptr),
@@ -116,6 +117,7 @@
         module_(std::move(m)),
         consumer_(std::move(c)),
         def_use_mgr_(nullptr),
+        feature_mgr_(nullptr),
         valid_analyses_(kAnalysisNone),
         type_mgr_(nullptr),
         id_to_name_(nullptr),
@@ -190,8 +192,8 @@
   inline IteratorRange<Module::const_inst_iterator> debugs3() const;
 
   // Iterators for debug info instructions (excluding OpLine & OpNoLine)
-  // contained in this module.  These are OpExtInst for OpenCL.DebugInfo.100
-  // or DebugInfo extension placed between section 9 and 10.
+  // contained in this module.  These are OpExtInst for DebugInfo extension
+  // placed between section 9 and 10.
   inline Module::inst_iterator ext_inst_debuginfo_begin();
   inline Module::inst_iterator ext_inst_debuginfo_end();
   inline IteratorRange<Module::inst_iterator> ext_inst_debuginfo();
@@ -403,8 +405,10 @@
   // instruction exists.
   Instruction* KillInst(Instruction* inst);
 
-  // Removes the non-semantic instruction tree that uses |inst|'s result id.
-  void KillNonSemanticInfo(Instruction* inst);
+  // Collects the non-semantic instruction tree that uses |inst|'s result id
+  // to be killed later.
+  void CollectNonSemanticTree(Instruction* inst,
+                              std::unordered_set<Instruction*>* to_kill);
 
   // Returns true if all of the given analyses are valid.
   bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; }
@@ -596,10 +600,14 @@
   bool ProcessCallTreeFromRoots(ProcessFunction& pfn,
                                 std::queue<uint32_t>* roots);
 
-  // Emmits a error message to the message consumer indicating the error
+  // Emits a error message to the message consumer indicating the error
   // described by |message| occurred in |inst|.
   void EmitErrorMessage(std::string message, Instruction* inst);
 
+  // Returns true if and only if there is a path to |bb| from the entry block of
+  // the function that contains |bb|.
+  bool IsReachable(const opt::BasicBlock& bb);
+
  private:
   // Builds the def-use manager from scratch, even if it was already valid.
   void BuildDefUseManager() {
@@ -763,6 +771,8 @@
 
   // The instruction decoration manager for |module_|.
   std::unique_ptr<analysis::DecorationManager> decoration_mgr_;
+
+  // The feature manager for |module_|.
   std::unique_ptr<FeatureManager> feature_mgr_;
 
   // A map from instructions to the basic block they belong to. This mapping is
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index e443ebb..a82b530 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -19,6 +19,7 @@
 #include "DebugInfo.h"
 #include "OpenCLDebugInfo100.h"
 #include "source/ext_inst.h"
+#include "source/opt/ir_context.h"
 #include "source/opt/log.h"
 #include "source/opt/reflect.h"
 #include "source/util/make_unique.h"
@@ -37,26 +38,40 @@
       inst_index_(0),
       last_dbg_scope_(kNoDebugScope, kNoInlinedAt) {}
 
+bool IsLineInst(const spv_parsed_instruction_t* inst) {
+  const auto opcode = static_cast<SpvOp>(inst->opcode);
+  if (IsOpLineInst(opcode)) return true;
+  if (opcode != SpvOpExtInst) return false;
+  if (inst->ext_inst_type != SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100)
+    return false;
+  const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
+  const NonSemanticShaderDebugInfo100Instructions ext_inst_key =
+      NonSemanticShaderDebugInfo100Instructions(ext_inst_index);
+  return ext_inst_key == NonSemanticShaderDebugInfo100DebugLine ||
+         ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine;
+}
+
 bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
   ++inst_index_;
-  const auto opcode = static_cast<SpvOp>(inst->opcode);
-  if (IsDebugLineInst(opcode)) {
+  if (IsLineInst(inst)) {
     module()->SetContainsDebugInfo();
     last_line_inst_.reset();
-    dbg_line_info_.push_back(
-        Instruction(module()->context(), *inst, last_dbg_scope_));
+    dbg_line_info_.emplace_back(module()->context(), *inst, last_dbg_scope_);
     return true;
   }
 
   // If it is a DebugScope or DebugNoScope of debug extension, we do not
   // create a new instruction, but simply keep the information in
   // struct DebugScope.
+  const auto opcode = static_cast<SpvOp>(inst->opcode);
   if (opcode == SpvOpExtInst && spvExtInstIsDebugInfo(inst->ext_inst_type)) {
     const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
-    if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
-      const OpenCLDebugInfo100Instructions ext_inst_key =
-          OpenCLDebugInfo100Instructions(ext_inst_index);
-      if (ext_inst_key == OpenCLDebugInfo100DebugScope) {
+    if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
+        inst->ext_inst_type ==
+            SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
+      const CommonDebugInfoInstructions ext_inst_key =
+          CommonDebugInfoInstructions(ext_inst_index);
+      if (ext_inst_key == CommonDebugInfoDebugScope) {
         uint32_t inlined_at = 0;
         if (inst->num_words > kInlinedAtIndex)
           inlined_at = inst->words[kInlinedAtIndex];
@@ -65,7 +80,7 @@
         module()->SetContainsDebugInfo();
         return true;
       }
-      if (ext_inst_key == OpenCLDebugInfo100DebugNoScope) {
+      if (ext_inst_key == CommonDebugInfoDebugNoScope) {
         last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
         module()->SetContainsDebugInfo();
         return true;
@@ -94,14 +109,20 @@
       new Instruction(module()->context(), *inst, std::move(dbg_line_info_)));
   if (!spv_inst->dbg_line_insts().empty()) {
     if (extra_line_tracking_ &&
-        (spv_inst->dbg_line_insts().back().opcode() != SpvOpNoLine)) {
+        (!spv_inst->dbg_line_insts().back().IsNoLine())) {
       last_line_inst_ = std::unique_ptr<Instruction>(
           spv_inst->dbg_line_insts().back().Clone(module()->context()));
+      if (last_line_inst_->IsDebugLineInst())
+        last_line_inst_->SetResultId(module()->context()->TakeNextId());
     }
     dbg_line_info_.clear();
   } else if (last_line_inst_ != nullptr) {
     last_line_inst_->SetDebugScope(last_dbg_scope_);
     spv_inst->dbg_line_insts().push_back(*last_line_inst_);
+    last_line_inst_ = std::unique_ptr<Instruction>(
+        spv_inst->dbg_line_insts().back().Clone(module()->context()));
+    if (last_line_inst_->IsDebugLineInst())
+      last_line_inst_->SetResultId(module()->context()->TakeNextId());
   }
 
   const char* src = source_.c_str();
@@ -233,6 +254,35 @@
             default: {
               Errorf(consumer_, src, loc,
                      "Debug info extension instruction other than DebugScope, "
+                     "DebugNoScope, DebugFunctionDefinition, DebugDeclare, and "
+                     "DebugValue found inside function",
+                     opcode);
+              return false;
+            }
+          }
+        } else if (inst->ext_inst_type ==
+                   SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
+          const NonSemanticShaderDebugInfo100Instructions ext_inst_key =
+              NonSemanticShaderDebugInfo100Instructions(ext_inst_index);
+          switch (ext_inst_key) {
+            case NonSemanticShaderDebugInfo100DebugDeclare:
+            case NonSemanticShaderDebugInfo100DebugValue:
+            case NonSemanticShaderDebugInfo100DebugScope:
+            case NonSemanticShaderDebugInfo100DebugNoScope:
+            case NonSemanticShaderDebugInfo100DebugFunctionDefinition: {
+              if (block_ == nullptr) {  // Inside function but outside blocks
+                Errorf(consumer_, src, loc,
+                       "Debug info extension instruction found inside function "
+                       "but outside block",
+                       opcode);
+              } else {
+                block_->AddInstruction(std::move(spv_inst));
+              }
+              break;
+            }
+            default: {
+              Errorf(consumer_, src, loc,
+                     "Debug info extension instruction other than DebugScope, "
                      "DebugNoScope, DebugDeclare, and DebugValue found inside "
                      "function",
                      opcode);
diff --git a/source/opt/iterator.h b/source/opt/iterator.h
index 444d457..847e1bb 100644
--- a/source/opt/iterator.h
+++ b/source/opt/iterator.h
@@ -30,18 +30,14 @@
 // std::unique_ptr managed elements in the vector, behaving like we are using
 // std::vector<|ValueType|>.
 template <typename ValueType, bool IsConst = false>
-class UptrVectorIterator
-    : public std::iterator<std::random_access_iterator_tag,
-                           typename std::conditional<IsConst, const ValueType,
-                                                     ValueType>::type> {
+class UptrVectorIterator {
  public:
-  using super = std::iterator<
-      std::random_access_iterator_tag,
-      typename std::conditional<IsConst, const ValueType, ValueType>::type>;
+  using iterator_category = std::random_access_iterator_tag;
+  using value_type = ValueType;
 
-  using pointer = typename super::pointer;
-  using reference = typename super::reference;
-  using difference_type = typename super::difference_type;
+  using pointer = value_type*;
+  using reference = value_type&;
+  using difference_type = std::ptrdiff_t;
 
   // Type aliases. We need to apply constness properly if |IsConst| is true.
   using Uptr = std::unique_ptr<ValueType>;
@@ -146,7 +142,7 @@
 template <typename IteratorType>
 inline IteratorRange<IteratorType> make_range(IteratorType&& begin,
                                               IteratorType&& end) {
-  return {std::move(begin), std::move(end)};
+  return {std::forward<IteratorType>(begin), std::forward<IteratorType>(end)};
 }
 
 // Returns a (begin, end) iterator pair for the given container.
@@ -174,11 +170,7 @@
 //
 // Currently this iterator is always an input iterator.
 template <typename SubIterator, typename Predicate>
-class FilterIterator
-    : public std::iterator<
-          std::input_iterator_tag, typename SubIterator::value_type,
-          typename SubIterator::difference_type, typename SubIterator::pointer,
-          typename SubIterator::reference> {
+class FilterIterator {
  public:
   // Iterator interface.
   using iterator_category = typename SubIterator::iterator_category;
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 205cd7a..da9ba8c 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -184,8 +184,8 @@
 bool LocalAccessChainConvertPass::HasOnlySupportedRefs(uint32_t ptrId) {
   if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end()) return true;
   if (get_def_use_mgr()->WhileEachUser(ptrId, [this](Instruction* user) {
-        if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue ||
-            user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) {
+        if (user->GetCommonDebugOpcode() == CommonDebugInfoDebugValue ||
+            user->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
           return true;
         }
         SpvOp op = user->opcode();
@@ -333,6 +333,20 @@
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
+  // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise
+  // around unknown extended
+  // instruction sets even if they are non-semantic
+  for (auto& inst : context()->module()->ext_inst_imports()) {
+    assert(inst.opcode() == SpvOpExtInstImport &&
+           "Expecting an import of an extension's instruction set.");
+    const char* extension_name =
+        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
+    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
+        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
+                          32)) {
+      return false;
+    }
+  }
   return true;
 }
 
@@ -418,6 +432,10 @@
       "SPV_KHR_ray_query",
       "SPV_EXT_fragment_invocation_density",
       "SPV_KHR_terminate_invocation",
+      "SPV_KHR_subgroup_uniform_control_flow",
+      "SPV_KHR_integer_dot_product",
+      "SPV_EXT_shader_image_int64",
+      "SPV_KHR_non_semantic_info",
   });
 }
 
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index 5f35ee1..5fd4f65 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -31,9 +31,9 @@
 bool LocalSingleBlockLoadStoreElimPass::HasOnlySupportedRefs(uint32_t ptrId) {
   if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end()) return true;
   if (get_def_use_mgr()->WhileEachUser(ptrId, [this](Instruction* user) {
-        auto dbg_op = user->GetOpenCL100DebugOpcode();
-        if (dbg_op == OpenCLDebugInfo100DebugDeclare ||
-            dbg_op == OpenCLDebugInfo100DebugValue) {
+        auto dbg_op = user->GetCommonDebugOpcode();
+        if (dbg_op == CommonDebugInfoDebugDeclare ||
+            dbg_op == CommonDebugInfoDebugValue) {
           return true;
         }
         SpvOp op = user->opcode();
@@ -188,6 +188,20 @@
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
+  // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise
+  // around unknown extended
+  // instruction sets even if they are non-semantic
+  for (auto& inst : context()->module()->ext_inst_imports()) {
+    assert(inst.opcode() == SpvOpExtInstImport &&
+           "Expecting an import of an extension's instruction set.");
+    const char* extension_name =
+        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
+    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
+        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
+                          32)) {
+      return false;
+    }
+  }
   return true;
 }
 
@@ -209,7 +223,7 @@
     return LocalSingleBlockLoadStoreElim(fp);
   };
 
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
+  bool modified = context()->ProcessReachableCallTree(pfn);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
@@ -270,6 +284,10 @@
       "SPV_EXT_fragment_invocation_density",
       "SPV_EXT_physical_storage_buffer",
       "SPV_KHR_terminate_invocation",
+      "SPV_KHR_subgroup_uniform_control_flow",
+      "SPV_KHR_integer_dot_product",
+      "SPV_EXT_shader_image_int64",
+      "SPV_KHR_non_semantic_info",
   });
 }
 
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index b8d9091..051bcad 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -53,6 +53,20 @@
     if (extensions_allowlist_.find(extName) == extensions_allowlist_.end())
       return false;
   }
+  // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise
+  // around unknown extended
+  // instruction sets even if they are non-semantic
+  for (auto& inst : context()->module()->ext_inst_imports()) {
+    assert(inst.opcode() == SpvOpExtInstImport &&
+           "Expecting an import of an extension's instruction set.");
+    const char* extension_name =
+        reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
+    if (0 == std::strncmp(extension_name, "NonSemantic.", 12) &&
+        0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100",
+                          32)) {
+      return false;
+    }
+  }
   return true;
 }
 
@@ -67,7 +81,7 @@
   ProcessFunction pfn = [this](Function* fp) {
     return LocalSingleStoreElim(fp);
   };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
+  bool modified = context()->ProcessReachableCallTree(pfn);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
@@ -123,6 +137,10 @@
       "SPV_EXT_fragment_invocation_density",
       "SPV_EXT_physical_storage_buffer",
       "SPV_KHR_terminate_invocation",
+      "SPV_KHR_subgroup_uniform_control_flow",
+      "SPV_KHR_integer_dot_product",
+      "SPV_EXT_shader_image_int64",
+      "SPV_KHR_non_semantic_info",
   });
 }
 bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) {
@@ -175,7 +193,7 @@
     for (auto* decl : invisible_decls) {
       if (dominator_analysis->Dominates(store_inst, decl)) {
         context()->get_debug_info_mgr()->AddDebugValueForDecl(decl, value_id,
-                                                              decl);
+                                                              decl, store_inst);
         modified = true;
       }
     }
@@ -221,9 +239,9 @@
       case SpvOpCopyObject:
         break;
       case SpvOpExtInst: {
-        auto dbg_op = user->GetOpenCL100DebugOpcode();
-        if (dbg_op == OpenCLDebugInfo100DebugDeclare ||
-            dbg_op == OpenCLDebugInfo100DebugValue) {
+        auto dbg_op = user->GetCommonDebugOpcode();
+        if (dbg_op == CommonDebugInfoDebugDeclare ||
+            dbg_op == CommonDebugInfoDebugValue) {
           break;
         }
         return nullptr;
@@ -290,9 +308,9 @@
   bool modified = false;
   for (Instruction* use : uses) {
     if (use->opcode() == SpvOpStore) continue;
-    auto dbg_op = use->GetOpenCL100DebugOpcode();
-    if (dbg_op == OpenCLDebugInfo100DebugDeclare ||
-        dbg_op == OpenCLDebugInfo100DebugValue)
+    auto dbg_op = use->GetCommonDebugOpcode();
+    if (dbg_op == CommonDebugInfoDebugDeclare ||
+        dbg_op == CommonDebugInfoDebugValue)
       continue;
     if (use->opcode() == SpvOpLoad &&
         dominator_analysis->Dominates(store_inst, use)) {
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 6cdced4..aff191f 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -760,7 +760,7 @@
           IRContext::Analysis::kAnalysisInstrToBlockMapping);
   Instruction* new_branch = builder.AddBranch(new_target);
 
-  new_branch->set_dbg_line_insts(lines);
+  if (!lines.empty()) new_branch->AddDebugLine(&lines.back());
   new_branch->SetDebugScope(scope);
 }
 
@@ -797,6 +797,9 @@
   for (BasicBlock* block : loop_blocks_inorder_) {
     RemapOperands(block);
   }
+  for (auto& block_itr : blocks_to_add_) {
+    RemapOperands(block_itr.get());
+  }
 
   // Rewrite the last phis, since they may still reference the original phi.
   for (Instruction* last_phi : state_.previous_phis_) {
@@ -870,6 +873,10 @@
   def_use_mgr->AnalyzeInstDefUse(basic_block->GetLabelInst());
 
   for (Instruction& inst : *basic_block) {
+    // Do def/use analysis on new lines
+    for (auto& line : inst.dbg_line_insts())
+      def_use_mgr->AnalyzeInstDefUse(&line);
+
     uint32_t old_id = inst.result_id();
 
     // Ignore stores etc.
@@ -1095,6 +1102,10 @@
 Pass::Status LoopUnroller::Process() {
   bool changed = false;
   for (Function& f : *context()->module()) {
+    if (f.IsDeclaration()) {
+      continue;
+    }
+
     LoopDescriptor* LD = context()->GetLoopDescriptor(&f);
     for (Loop& loop : *LD) {
       LoopUtils loop_utils{context(), &loop};
diff --git a/source/opt/mem_pass.cpp b/source/opt/mem_pass.cpp
index 5738798..ca4889b 100644
--- a/source/opt/mem_pass.cpp
+++ b/source/opt/mem_pass.cpp
@@ -20,7 +20,6 @@
 #include <set>
 #include <vector>
 
-#include "OpenCLDebugInfo100.h"
 #include "source/cfa.h"
 #include "source/opt/basic_block.h"
 #include "source/opt/dominator_analysis.h"
@@ -87,8 +86,8 @@
   }
   const SpvOp op = ptrInst->opcode();
   if (op == SpvOpVariable || IsNonPtrAccessChain(op)) return true;
-  if (op != SpvOpFunctionParameter) return false;
   const uint32_t varTypeId = ptrInst->type_id();
+  if (varTypeId == 0) return false;
   const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId);
   return varTypeInst->opcode() == SpvOpTypePointer;
 }
@@ -226,9 +225,9 @@
 
 bool MemPass::HasOnlySupportedRefs(uint32_t varId) {
   return get_def_use_mgr()->WhileEachUser(varId, [this](Instruction* user) {
-    auto dbg_op = user->GetOpenCL100DebugOpcode();
-    if (dbg_op == OpenCLDebugInfo100DebugDeclare ||
-        dbg_op == OpenCLDebugInfo100DebugValue) {
+    auto dbg_op = user->GetCommonDebugOpcode();
+    if (dbg_op == CommonDebugInfoDebugDeclare ||
+        dbg_op == CommonDebugInfoDebugValue) {
       return true;
     }
     SpvOp op = user->opcode();
diff --git a/source/opt/mem_pass.h b/source/opt/mem_pass.h
index dcc16b6..5a77670 100644
--- a/source/opt/mem_pass.h
+++ b/source/opt/mem_pass.h
@@ -38,7 +38,7 @@
 // utility functions and supporting state.
 class MemPass : public Pass {
  public:
-  virtual ~MemPass() = default;
+  virtual ~MemPass() override = default;
 
   // Returns an undef value for the given |var_id|'s type.
   uint32_t GetUndefVal(uint32_t var_id) {
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index f160104..a962a7c 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -111,7 +111,9 @@
   }
 
   RecordImmediateDominators(function);
-  AddSingleCaseSwitchAroundFunction();
+  if (!AddSingleCaseSwitchAroundFunction()) {
+    return false;
+  }
 
   std::list<BasicBlock*> order;
   cfg()->ComputeStructuredOrder(function, &*function->begin(), &order);
@@ -770,7 +772,7 @@
   list->insert(pos, new_element);
 }
 
-void MergeReturnPass::AddSingleCaseSwitchAroundFunction() {
+bool MergeReturnPass::AddSingleCaseSwitchAroundFunction() {
   CreateReturnBlock();
   CreateReturn(final_return_block_);
 
@@ -778,7 +780,10 @@
     cfg()->RegisterBlock(final_return_block_);
   }
 
-  CreateSingleCaseSwitch(final_return_block_);
+  if (!CreateSingleCaseSwitch(final_return_block_)) {
+    return false;
+  }
+  return true;
 }
 
 BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) {
@@ -813,7 +818,7 @@
   return new_block;
 }
 
-void MergeReturnPass::CreateSingleCaseSwitch(BasicBlock* merge_target) {
+bool MergeReturnPass::CreateSingleCaseSwitch(BasicBlock* merge_target) {
   // Insert the switch before any code is run.  We have to split the entry
   // block to make sure the OpVariable instructions remain in the entry block.
   BasicBlock* start_block = &*function_->begin();
@@ -830,13 +835,17 @@
       context(), start_block,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
 
-  builder.AddSwitch(builder.GetUintConstantId(0u), old_block->id(), {},
-                    merge_target->id());
+  uint32_t const_zero_id = builder.GetUintConstantId(0u);
+  if (const_zero_id == 0) {
+    return false;
+  }
+  builder.AddSwitch(const_zero_id, old_block->id(), {}, merge_target->id());
 
   if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) {
     cfg()->RegisterBlock(old_block);
     cfg()->AddEdges(start_block);
   }
+  return true;
 }
 
 bool MergeReturnPass::HasNontrivialUnreachableBlocks(Function* function) {
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index 06a3e7b..4096ce7 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -277,7 +277,7 @@
   // current function where the switch and case value are both zero and the
   // default is the merge block. Returns after the switch is executed. Sets
   // |final_return_block_|.
-  void AddSingleCaseSwitchAroundFunction();
+  bool AddSingleCaseSwitchAroundFunction();
 
   // Creates a new basic block that branches to |header_label_id|.  Returns the
   // new basic block.  The block will be the second last basic block in the
@@ -286,7 +286,7 @@
 
   // Creates a one case switch around the executable code of the function with
   // |merge_target| as the merge node.
-  void CreateSingleCaseSwitch(BasicBlock* merge_target);
+  bool CreateSingleCaseSwitch(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
diff --git a/source/opt/module.cpp b/source/opt/module.cpp
index 0c88601..f97defb 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -145,20 +145,21 @@
   DebugScope last_scope(kNoDebugScope, kNoInlinedAt);
   const Instruction* last_line_inst = nullptr;
   bool between_merge_and_branch = false;
+  bool between_label_and_phi_var = false;
   auto write_inst = [binary, skip_nop, &last_scope, &last_line_inst,
-                     &between_merge_and_branch, this](const Instruction* i) {
+                     &between_merge_and_branch, &between_label_and_phi_var,
+                     this](const Instruction* i) {
     // Skip emitting line instructions between merge and branch instructions.
     auto opcode = i->opcode();
-    if (between_merge_and_branch &&
-        (opcode == SpvOpLine || opcode == SpvOpNoLine)) {
+    if (between_merge_and_branch && i->IsLineInst()) {
       return;
     }
     between_merge_and_branch = false;
     if (last_line_inst != nullptr) {
-      // If the current instruction is OpLine and it is the same with
-      // the last line instruction that is still effective (can be applied
+      // If the current instruction is OpLine or DebugLine and it is the same
+      // as the last line instruction that is still effective (can be applied
       // to the next instruction), we skip writing the current instruction.
-      if (opcode == SpvOpLine) {
+      if (i->IsLine()) {
         uint32_t operand_index = 0;
         if (last_line_inst->WhileEachInOperand(
                 [&operand_index, i](const uint32_t* word) {
@@ -167,39 +168,67 @@
                 })) {
           return;
         }
-      } else if (opcode != SpvOpNoLine && i->dbg_line_insts().empty()) {
+      } else if (!i->IsNoLine() && i->dbg_line_insts().empty()) {
         // If the current instruction does not have the line information,
         // the last line information is not effective any more. Emit OpNoLine
-        // to specify it.
-        binary->push_back((1 << 16) | static_cast<uint16_t>(SpvOpNoLine));
+        // or DebugNoLine to specify it.
+        uint32_t shader_set_id = context()
+                                     ->get_feature_mgr()
+                                     ->GetExtInstImportId_Shader100DebugInfo();
+        if (shader_set_id != 0) {
+          binary->push_back((5 << 16) | static_cast<uint16_t>(SpvOpExtInst));
+          binary->push_back(context()->get_type_mgr()->GetVoidTypeId());
+          binary->push_back(context()->TakeNextId());
+          binary->push_back(shader_set_id);
+          binary->push_back(NonSemanticShaderDebugInfo100DebugNoLine);
+        } else {
+          binary->push_back((1 << 16) | static_cast<uint16_t>(SpvOpNoLine));
+        }
         last_line_inst = nullptr;
       }
     }
+
+    if (opcode == SpvOpLabel) {
+      between_label_and_phi_var = true;
+    } else if (opcode != SpvOpVariable && opcode != SpvOpPhi &&
+               !spvtools::opt::IsOpLineInst(opcode)) {
+      between_label_and_phi_var = false;
+    }
+
     if (!(skip_nop && i->IsNop())) {
       const auto& scope = i->GetDebugScope();
       if (scope != last_scope) {
-        // Emit DebugScope |scope| to |binary|.
-        auto dbg_inst = ext_inst_debuginfo_.begin();
-        scope.ToBinary(dbg_inst->type_id(), context()->TakeNextId(),
-                       dbg_inst->GetSingleWordOperand(2), binary);
+        // Can only emit nonsemantic instructions after all phi instructions
+        // in a block so don't emit scope instructions before phi instructions
+        // for NonSemantic.Shader.DebugInfo.100.
+        if (!between_label_and_phi_var ||
+            context()
+                ->get_feature_mgr()
+                ->GetExtInstImportId_OpenCL100DebugInfo()) {
+          // Emit DebugScope |scope| to |binary|.
+          auto dbg_inst = ext_inst_debuginfo_.begin();
+          scope.ToBinary(dbg_inst->type_id(), context()->TakeNextId(),
+                         dbg_inst->GetSingleWordOperand(2), binary);
+        }
         last_scope = scope;
       }
 
       i->ToBinaryWithoutAttachedDebugInsts(binary);
     }
     // Update the last line instruction.
-    if (spvOpcodeIsBlockTerminator(opcode) || opcode == SpvOpNoLine) {
+    if (spvOpcodeIsBlockTerminator(opcode) || i->IsNoLine()) {
       last_line_inst = nullptr;
     } else if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge) {
       between_merge_and_branch = true;
       last_line_inst = nullptr;
-    } else if (opcode == SpvOpLine) {
+    } else if (i->IsLine()) {
       last_line_inst = i;
     }
   };
   ForEachInst(write_inst, true);
 
-  // We create new instructions for DebugScope. The bound must be updated.
+  // We create new instructions for DebugScope and DebugNoLine. The bound must
+  // be updated.
   binary->data()[bound_idx] = header_.bound;
 }
 
diff --git a/source/opt/module.h b/source/opt/module.h
index d609b60..0360b7d 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -103,8 +103,8 @@
   // This is due to decision by the SPIR Working Group, pending publication.
   inline void AddDebug3Inst(std::unique_ptr<Instruction> d);
 
-  // Appends a debug info extension (OpenCL.DebugInfo.100 or DebugInfo)
-  // instruction to this module.
+  // Appends a debug info extension (OpenCL.DebugInfo.100,
+  // NonSemantic.Shader.DebugInfo.100, or DebugInfo) instruction to this module.
   inline void AddExtInstDebugInfo(std::unique_ptr<Instruction> d);
 
   // Appends an annotation instruction to this module.
@@ -192,8 +192,8 @@
   inline IteratorRange<const_inst_iterator> debugs3() const;
 
   // Iterators for debug info instructions (excluding OpLine & OpNoLine)
-  // contained in this module.  These are OpExtInst for OpenCL.DebugInfo.100
-  // or DebugInfo extension placed between section 9 and 10.
+  // contained in this module.  These are OpExtInst for DebugInfo extension
+  // placed between section 9 and 10.
   inline inst_iterator ext_inst_debuginfo_begin();
   inline inst_iterator ext_inst_debuginfo_end();
   inline IteratorRange<inst_iterator> ext_inst_debuginfo();
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 909442c..e74db26 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -155,7 +155,8 @@
           .RegisterPass(CreateVectorDCEPass())
           .RegisterPass(CreateDeadInsertElimPass())
           .RegisterPass(CreateReduceLoadSizePass())
-          .RegisterPass(CreateAggressiveDCEPass());
+          .RegisterPass(CreateAggressiveDCEPass())
+          .RegisterPass(CreateInterpolateFixupPass());
 }
 
 Optimizer& Optimizer::RegisterPerformancePasses() {
@@ -319,6 +320,8 @@
     RegisterPass(CreateCombineAccessChainsPass());
   } else if (pass_name == "convert-local-access-chains") {
     RegisterPass(CreateLocalAccessChainConvertPass());
+  } else if (pass_name == "replace-desc-array-access-using-var-index") {
+    RegisterPass(CreateReplaceDescArrayAccessUsingVarIndexPass());
   } else if (pass_name == "descriptor-scalar-replacement") {
     RegisterPass(CreateDescriptorScalarReplacementPass());
   } else if (pass_name == "eliminate-dead-code-aggressive") {
@@ -384,7 +387,23 @@
   } else if (pass_name == "loop-invariant-code-motion") {
     RegisterPass(CreateLoopInvariantCodeMotionPass());
   } else if (pass_name == "reduce-load-size") {
-    RegisterPass(CreateReduceLoadSizePass());
+    if (pass_args.size() == 0) {
+      RegisterPass(CreateReduceLoadSizePass());
+    } else {
+      double load_replacement_threshold = 0.9;
+      if (pass_args.find_first_not_of(".0123456789") == std::string::npos) {
+        load_replacement_threshold = atof(pass_args.c_str());
+      }
+
+      if (load_replacement_threshold >= 0) {
+        RegisterPass(CreateReduceLoadSizePass(load_replacement_threshold));
+      } else {
+        Error(consumer(), nullptr, {},
+              "--reduce-load-size must have no arguments or a non-negative "
+              "double argument");
+        return false;
+      }
+    }
   } else if (pass_name == "redundancy-elimination") {
     RegisterPass(CreateRedundancyEliminationPass());
   } else if (pass_name == "private-to-local") {
@@ -400,22 +419,22 @@
     RegisterPass(CreateSimplificationPass());
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
-    RegisterPass(CreateAggressiveDCEPass());
+    RegisterPass(CreateAggressiveDCEPass(true));
   } else if (pass_name == "inst-desc-idx-check") {
     RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true));
     RegisterPass(CreateSimplificationPass());
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
-    RegisterPass(CreateAggressiveDCEPass());
+    RegisterPass(CreateAggressiveDCEPass(true));
   } else if (pass_name == "inst-buff-oob-check") {
     RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, true, true));
     RegisterPass(CreateSimplificationPass());
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
-    RegisterPass(CreateAggressiveDCEPass());
+    RegisterPass(CreateAggressiveDCEPass(true));
   } else if (pass_name == "inst-buff-addr-check") {
     RegisterPass(CreateInstBuffAddrCheckPass(7, 23));
-    RegisterPass(CreateAggressiveDCEPass());
+    RegisterPass(CreateAggressiveDCEPass(true));
   } else if (pass_name == "convert-relaxed-to-half") {
     RegisterPass(CreateConvertRelaxedToHalfPass());
   } else if (pass_name == "relax-float-ops") {
@@ -488,12 +507,36 @@
     RegisterSizePasses();
   } else if (pass_name == "legalize-hlsl") {
     RegisterLegalizationPasses();
+  } else if (pass_name == "remove-unused-interface-variables") {
+    RegisterPass(CreateRemoveUnusedInterfaceVariablesPass());
   } else if (pass_name == "graphics-robust-access") {
     RegisterPass(CreateGraphicsRobustAccessPass());
   } else if (pass_name == "wrap-opkill") {
     RegisterPass(CreateWrapOpKillPass());
   } else if (pass_name == "amd-ext-to-khr") {
     RegisterPass(CreateAmdExtToKhrPass());
+  } else if (pass_name == "interpolate-fixup") {
+    RegisterPass(CreateInterpolateFixupPass());
+  } else if (pass_name == "convert-to-sampled-image") {
+    if (pass_args.size() > 0) {
+      auto descriptor_set_binding_pairs =
+          opt::ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString(
+              pass_args.c_str());
+      if (!descriptor_set_binding_pairs) {
+        Errorf(consumer(), nullptr, {},
+               "Invalid argument for --convert-to-sampled-image: %s",
+               pass_args.c_str());
+        return false;
+      }
+      RegisterPass(CreateConvertToSampledImagePass(
+          std::move(*descriptor_set_binding_pairs)));
+    } else {
+      Errorf(consumer(), nullptr, {},
+             "Invalid pairs of descriptor set and binding '%s'. Expected a "
+             "string of <descriptor set>:<binding> pairs.",
+             pass_args.c_str());
+      return false;
+    }
   } else {
     Errorf(consumer(), nullptr, {},
            "Unknown flag '--%s'. Use --help for a list of valid flags",
@@ -721,9 +764,14 @@
       MakeUnique<opt::SSARewritePass>());
 }
 
-Optimizer::PassToken CreateAggressiveDCEPass() {
+Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface) {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::AggressiveDCEPass>());
+      MakeUnique<opt::AggressiveDCEPass>(preserve_interface));
+}
+
+Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::RemoveUnusedInterfaceVariablesPass>());
 }
 
 Optimizer::PassToken CreatePropagateLineInfoPass() {
@@ -849,9 +897,10 @@
   return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::VectorDCE>());
 }
 
-Optimizer::PassToken CreateReduceLoadSizePass() {
+Optimizer::PassToken CreateReduceLoadSizePass(
+    double load_replacement_threshold) {
   return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::ReduceLoadSize>());
+      MakeUnique<opt::ReduceLoadSize>(load_replacement_threshold));
 }
 
 Optimizer::PassToken CreateCombineAccessChainsPass() {
@@ -911,6 +960,11 @@
       MakeUnique<opt::GraphicsRobustAccessPass>());
 }
 
+Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::ReplaceDescArrayAccessUsingVarIndex>());
+}
+
 Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::DescriptorScalarReplacement>());
@@ -925,4 +979,16 @@
       MakeUnique<opt::AmdExtensionToKhrPass>());
 }
 
+Optimizer::PassToken CreateInterpolateFixupPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::InterpFixupPass>());
+}
+
+Optimizer::PassToken CreateConvertToSampledImagePass(
+    const std::vector<opt::DescriptorSetAndBinding>&
+        descriptor_set_binding_pairs) {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::ConvertToSampledImagePass>(descriptor_set_binding_pairs));
+}
+
 }  // namespace spvtools
diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp
index 09b78af..017aad1 100644
--- a/source/opt/pass.cpp
+++ b/source/opt/pass.cpp
@@ -43,8 +43,8 @@
   if (status == Status::SuccessWithChange) {
     ctx->InvalidateAnalysesExceptFor(GetPreservedAnalyses());
   }
-  assert((status == Status::Failure || ctx->IsConsistent()) &&
-         "An analysis in the context is out of date.");
+  if (!(status == Status::Failure || ctx->IsConsistent()))
+    assert(false && "An analysis in the context is out of date.");
   return status;
 }
 
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 1bc94c7..f3c30d5 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -26,6 +26,7 @@
 #include "source/opt/combine_access_chains.h"
 #include "source/opt/compact_ids_pass.h"
 #include "source/opt/convert_to_half_pass.h"
+#include "source/opt/convert_to_sampled_image_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"
@@ -46,6 +47,7 @@
 #include "source/opt/inst_bindless_check_pass.h"
 #include "source/opt/inst_buff_addr_check_pass.h"
 #include "source/opt/inst_debug_printf_pass.h"
+#include "source/opt/interp_fixup_pass.h"
 #include "source/opt/licm_pass.h"
 #include "source/opt/local_access_chain_convert_pass.h"
 #include "source/opt/local_redundancy_elimination.h"
@@ -63,6 +65,8 @@
 #include "source/opt/redundancy_elimination.h"
 #include "source/opt/relax_float_ops_pass.h"
 #include "source/opt/remove_duplicates_pass.h"
+#include "source/opt/remove_unused_interface_variables_pass.h"
+#include "source/opt/replace_desc_array_access_using_var_index.h"
 #include "source/opt/replace_invalid_opc.h"
 #include "source/opt/scalar_replacement_pass.h"
 #include "source/opt/set_spec_constant_default_value_pass.h"
diff --git a/source/opt/private_to_local_pass.cpp b/source/opt/private_to_local_pass.cpp
index dd6cbbd..12a226d 100644
--- a/source/opt/private_to_local_pass.cpp
+++ b/source/opt/private_to_local_pass.cpp
@@ -157,8 +157,7 @@
 bool PrivateToLocalPass::IsValidUse(const Instruction* inst) const {
   // The cases in this switch have to match the cases in |UpdateUse|.
   // If we don't know how to update it, it is not valid.
-  if (inst->GetOpenCL100DebugOpcode() ==
-      OpenCLDebugInfo100DebugGlobalVariable) {
+  if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugGlobalVariable) {
     return true;
   }
   switch (inst->opcode()) {
@@ -183,8 +182,7 @@
   // The cases in this switch have to match the cases in |IsValidUse|.  If we
   // don't think it is valid, the optimization will not view the variable as a
   // candidate, and therefore the use will not be updated.
-  if (inst->GetOpenCL100DebugOpcode() ==
-      OpenCLDebugInfo100DebugGlobalVariable) {
+  if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugGlobalVariable) {
     context()->get_debug_info_mgr()->ConvertDebugGlobalToLocalVariable(inst,
                                                                        user);
     return true;
diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp
index c58adf4..e9b8087 100644
--- a/source/opt/reduce_load_size.cpp
+++ b/source/opt/reduce_load_size.cpp
@@ -27,7 +27,6 @@
 const uint32_t kExtractCompositeIdInIdx = 0;
 const uint32_t kVariableStorageClassInIdx = 0;
 const uint32_t kLoadPointerInIdx = 0;
-const double kThreshold = 0.9;
 
 }  // namespace
 
@@ -139,7 +138,7 @@
 
   all_elements_used =
       !def_use_mgr->WhileEachUser(op_inst, [&elements_used](Instruction* use) {
-        if (use->IsOpenCL100DebugInstr()) return true;
+        if (use->IsCommonDebugInstr()) return true;
         if (use->opcode() != SpvOpCompositeExtract ||
             use->NumInOperands() == 1) {
           return false;
@@ -151,6 +150,8 @@
   bool should_replace = false;
   if (all_elements_used) {
     should_replace = false;
+  } else if (1.0 <= replacement_threshold_) {
+    should_replace = true;
   } else {
     analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
     analysis::TypeManager* type_mgr = context()->get_type_mgr();
@@ -172,7 +173,7 @@
     }
     double percent_used = static_cast<double>(elements_used.size()) /
                           static_cast<double>(total_size);
-    should_replace = (percent_used < kThreshold);
+    should_replace = (percent_used < replacement_threshold_);
   }
 
   should_replace_cache_[op_inst->result_id()] = should_replace;
diff --git a/source/opt/reduce_load_size.h b/source/opt/reduce_load_size.h
index ccac49b..b323845 100644
--- a/source/opt/reduce_load_size.h
+++ b/source/opt/reduce_load_size.h
@@ -27,6 +27,9 @@
 // See optimizer.hpp for documentation.
 class ReduceLoadSize : public Pass {
  public:
+  explicit ReduceLoadSize(double replacement_threshold)
+      : replacement_threshold_(replacement_threshold) {}
+
   const char* name() const override { return "reduce-load-size"; }
   Status Process() override;
 
@@ -54,6 +57,11 @@
   // on the load feeding |inst|.
   bool ShouldReplaceExtract(Instruction* inst);
 
+  // Threshold to determine whether we have to replace the load or not. If the
+  // ratio of the used components of the load is less than the threshold, we
+  // replace the load.
+  double replacement_threshold_;
+
   // Maps the result id of an OpLoad instruction to the result of whether or
   // not the OpCompositeExtract that use the id should be replaced.
   std::unordered_map<uint32_t, bool> should_replace_cache_;
diff --git a/source/opt/redundancy_elimination.cpp b/source/opt/redundancy_elimination.cpp
index 362e54d..398225b 100644
--- a/source/opt/redundancy_elimination.cpp
+++ b/source/opt/redundancy_elimination.cpp
@@ -24,6 +24,10 @@
   ValueNumberTable vnTable(context());
 
   for (auto& func : *get_module()) {
+    if (func.IsDeclaration()) {
+      continue;
+    }
+
     // Build the dominator tree for this function. It is how the code is
     // traversed.
     DominatorTree& dom_tree =
diff --git a/source/opt/reflect.h b/source/opt/reflect.h
index c7d46df..ffd2805 100644
--- a/source/opt/reflect.h
+++ b/source/opt/reflect.h
@@ -34,7 +34,7 @@
 inline bool IsDebug3Inst(SpvOp opcode) {
   return opcode == SpvOpModuleProcessed;
 }
-inline bool IsDebugLineInst(SpvOp opcode) {
+inline bool IsOpLineInst(SpvOp opcode) {
   return opcode == SpvOpLine || opcode == SpvOpNoLine;
 }
 inline bool IsAnnotationInst(SpvOp opcode) {
@@ -51,7 +51,8 @@
          opcode == SpvOpTypeCooperativeMatrixNV;
 }
 inline bool IsConstantInst(SpvOp opcode) {
-  return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp;
+  return (opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp) ||
+         opcode == SpvOpConstFunctionPointerINTEL;
 }
 inline bool IsCompileTimeConstantInst(SpvOp opcode) {
   return opcode >= SpvOpConstantTrue && opcode <= SpvOpConstantNull;
diff --git a/source/opt/relax_float_ops_pass.cpp b/source/opt/relax_float_ops_pass.cpp
index 73f16dd..3fcf879 100644
--- a/source/opt/relax_float_ops_pass.cpp
+++ b/source/opt/relax_float_ops_pass.cpp
@@ -76,7 +76,7 @@
   Pass::ProcessFunction pfn = [this](Function* fp) {
     return ProcessFunction(fp);
   };
-  bool modified = context()->ProcessEntryPointCallTree(pfn);
+  bool modified = context()->ProcessReachableCallTree(pfn);
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
diff --git a/source/opt/remove_unused_interface_variables_pass.cpp b/source/opt/remove_unused_interface_variables_pass.cpp
new file mode 100644
index 0000000..31e87bd
--- /dev/null
+++ b/source/opt/remove_unused_interface_variables_pass.cpp
@@ -0,0 +1,93 @@
+// Copyright (c) 2021 ZHOU He
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 "remove_unused_interface_variables_pass.h"
+#include "source/spirv_constant.h"
+namespace spvtools {
+namespace opt {
+
+class RemoveUnusedInterfaceVariablesContext {
+  RemoveUnusedInterfaceVariablesPass& parent_;
+  Instruction& entry_;
+  std::unordered_set<uint32_t> used_variables_;
+  IRContext::ProcessFunction pfn_ =
+      std::bind(&RemoveUnusedInterfaceVariablesContext::processFunction, this,
+                std::placeholders::_1);
+
+  bool processFunction(Function* func) {
+    for (const auto& basic_block : *func)
+      for (const auto& instruction : basic_block)
+        instruction.ForEachInId([&](const uint32_t* id) {
+          if (used_variables_.count(*id)) return;
+          auto* var = parent_.get_def_use_mgr()->GetDef(*id);
+          if (!var || var->opcode() != SpvOpVariable) return;
+          auto storage_class = var->GetSingleWordInOperand(0);
+          if (storage_class != SpvStorageClassFunction &&
+              (parent_.get_module()->version() >=
+                   SPV_SPIRV_VERSION_WORD(1, 4) ||
+               storage_class == SpvStorageClassInput ||
+               storage_class == SpvStorageClassOutput))
+            used_variables_.insert(*id);
+        });
+    return false;
+  }
+
+ public:
+  RemoveUnusedInterfaceVariablesContext(
+      RemoveUnusedInterfaceVariablesPass& parent, Instruction& entry)
+      : parent_(parent), entry_(entry) {}
+
+  void CollectUsedVariables() {
+    std::queue<uint32_t> roots;
+    roots.push(entry_.GetSingleWordInOperand(1));
+    parent_.context()->ProcessCallTreeFromRoots(pfn_, &roots);
+  }
+
+  bool ShouldModify() {
+    std::unordered_set<uint32_t> old_variables;
+    for (int i = entry_.NumInOperands() - 1; i >= 3; --i) {
+      auto variable = entry_.GetInOperand(i).words[0];
+      if (!used_variables_.count(variable)) return true;  // It is unused.
+      if (old_variables.count(variable)) return true;     // It is duplicate.
+      old_variables.insert(variable);
+    }
+    if (old_variables.size() != used_variables_.size())  // Missing IDs.
+      return true;
+    return false;
+  }
+
+  void Modify() {
+    for (int i = entry_.NumInOperands() - 1; i >= 3; --i)
+      entry_.RemoveInOperand(i);
+    for (auto id : used_variables_) {
+      entry_.AddOperand(Operand(SPV_OPERAND_TYPE_ID, {id}));
+    }
+  }
+};
+
+RemoveUnusedInterfaceVariablesPass::Status
+RemoveUnusedInterfaceVariablesPass::Process() {
+  bool modified = false;
+  for (auto& entry : get_module()->entry_points()) {
+    RemoveUnusedInterfaceVariablesContext context(*this, entry);
+    context.CollectUsedVariables();
+    if (context.ShouldModify()) {
+      context.Modify();
+      modified = true;
+    }
+  }
+  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
+}
+}  // namespace opt
+}  // namespace spvtools
\ No newline at end of file
diff --git a/source/opt/remove_unused_interface_variables_pass.h b/source/opt/remove_unused_interface_variables_pass.h
new file mode 100644
index 0000000..7f11187
--- /dev/null
+++ b/source/opt/remove_unused_interface_variables_pass.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2021 ZHOU He
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pass.h"
+namespace spvtools {
+namespace opt {
+
+class RemoveUnusedInterfaceVariablesPass : public Pass {
+  const char* name() const override {
+    return "remove-unused-interface-variables-pass";
+  }
+  Status Process() override;
+};
+}  // namespace opt
+}  // namespace spvtools
\ No newline at end of file
diff --git a/source/opt/replace_desc_array_access_using_var_index.cpp b/source/opt/replace_desc_array_access_using_var_index.cpp
new file mode 100644
index 0000000..1082e67
--- /dev/null
+++ b/source/opt/replace_desc_array_access_using_var_index.cpp
@@ -0,0 +1,423 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/replace_desc_array_access_using_var_index.h"
+
+#include "source/opt/desc_sroa_util.h"
+#include "source/opt/ir_builder.h"
+#include "source/util/string_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+const uint32_t kOpAccessChainInOperandIndexes = 1;
+const uint32_t kOpTypePointerInOperandType = 1;
+const uint32_t kOpTypeArrayInOperandType = 0;
+const uint32_t kOpTypeStructInOperandMember = 0;
+IRContext::Analysis kAnalysisDefUseAndInstrToBlockMapping =
+    IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping;
+
+uint32_t GetValueWithKeyExistenceCheck(
+    uint32_t key, const std::unordered_map<uint32_t, uint32_t>& map) {
+  auto itr = map.find(key);
+  assert(itr != map.end() && "Key does not exist");
+  return itr->second;
+}
+
+}  // namespace
+
+Pass::Status ReplaceDescArrayAccessUsingVarIndex::Process() {
+  Status status = Status::SuccessWithoutChange;
+  for (Instruction& var : context()->types_values()) {
+    if (descsroautil::IsDescriptorArray(context(), &var)) {
+      if (ReplaceVariableAccessesWithConstantElements(&var))
+        status = Status::SuccessWithChange;
+    }
+  }
+  return status;
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::
+    ReplaceVariableAccessesWithConstantElements(Instruction* var) const {
+  std::vector<Instruction*> work_list;
+  get_def_use_mgr()->ForEachUser(var, [&work_list](Instruction* use) {
+    switch (use->opcode()) {
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+        work_list.push_back(use);
+        break;
+      default:
+        break;
+    }
+  });
+
+  bool updated = false;
+  for (Instruction* access_chain : work_list) {
+    if (descsroautil::GetAccessChainIndexAsConst(context(), access_chain) ==
+        nullptr) {
+      ReplaceAccessChain(var, access_chain);
+      updated = true;
+    }
+  }
+  // Note that we do not consider OpLoad and OpCompositeExtract because
+  // OpCompositeExtract always has constant literals for indices.
+  return updated;
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplaceAccessChain(
+    Instruction* var, Instruction* access_chain) const {
+  uint32_t number_of_elements =
+      descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var);
+  assert(number_of_elements != 0 && "Number of element is 0");
+  if (number_of_elements == 1) {
+    UseConstIndexForAccessChain(access_chain, 0);
+    get_def_use_mgr()->AnalyzeInstUse(access_chain);
+    return;
+  }
+  ReplaceUsersOfAccessChain(access_chain, number_of_elements);
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplaceUsersOfAccessChain(
+    Instruction* access_chain, uint32_t number_of_elements) const {
+  std::vector<Instruction*> final_users;
+  CollectRecursiveUsersWithConcreteType(access_chain, &final_users);
+  for (auto* inst : final_users) {
+    std::deque<Instruction*> insts_to_be_cloned =
+        CollectRequiredImageInsts(inst);
+    ReplaceNonUniformAccessWithSwitchCase(
+        inst, access_chain, number_of_elements, insts_to_be_cloned);
+  }
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::CollectRecursiveUsersWithConcreteType(
+    Instruction* access_chain, std::vector<Instruction*>* final_users) const {
+  std::queue<Instruction*> work_list;
+  work_list.push(access_chain);
+  while (!work_list.empty()) {
+    auto* inst_from_work_list = work_list.front();
+    work_list.pop();
+    get_def_use_mgr()->ForEachUser(
+        inst_from_work_list, [this, final_users, &work_list](Instruction* use) {
+          // TODO: Support Boolean type as well.
+          if (!use->HasResultId() || IsConcreteType(use->type_id())) {
+            final_users->push_back(use);
+          } else {
+            work_list.push(use);
+          }
+        });
+  }
+}
+
+std::deque<Instruction*>
+ReplaceDescArrayAccessUsingVarIndex::CollectRequiredImageInsts(
+    Instruction* user_of_image_insts) const {
+  std::unordered_set<uint32_t> seen_inst_ids;
+  std::queue<Instruction*> work_list;
+
+  auto decision_to_include_operand = [this, &seen_inst_ids,
+                                      &work_list](uint32_t* idp) {
+    if (!seen_inst_ids.insert(*idp).second) return;
+    Instruction* operand = get_def_use_mgr()->GetDef(*idp);
+    if (context()->get_instr_block(operand) != nullptr &&
+        HasImageOrImagePtrType(operand)) {
+      work_list.push(operand);
+    }
+  };
+
+  std::deque<Instruction*> required_image_insts;
+  required_image_insts.push_front(user_of_image_insts);
+  user_of_image_insts->ForEachInId(decision_to_include_operand);
+  while (!work_list.empty()) {
+    auto* inst_from_work_list = work_list.front();
+    work_list.pop();
+    required_image_insts.push_front(inst_from_work_list);
+    inst_from_work_list->ForEachInId(decision_to_include_operand);
+  }
+  return required_image_insts;
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::HasImageOrImagePtrType(
+    const Instruction* inst) const {
+  assert(inst != nullptr && inst->type_id() != 0 && "Invalid instruction");
+  return IsImageOrImagePtrType(get_def_use_mgr()->GetDef(inst->type_id()));
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::IsImageOrImagePtrType(
+    const Instruction* type_inst) const {
+  if (type_inst->opcode() == SpvOpTypeImage ||
+      type_inst->opcode() == SpvOpTypeSampler ||
+      type_inst->opcode() == SpvOpTypeSampledImage) {
+    return true;
+  }
+  if (type_inst->opcode() == SpvOpTypePointer) {
+    Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(
+        type_inst->GetSingleWordInOperand(kOpTypePointerInOperandType));
+    return IsImageOrImagePtrType(pointee_type_inst);
+  }
+  if (type_inst->opcode() == SpvOpTypeArray) {
+    Instruction* element_type_inst = get_def_use_mgr()->GetDef(
+        type_inst->GetSingleWordInOperand(kOpTypeArrayInOperandType));
+    return IsImageOrImagePtrType(element_type_inst);
+  }
+  if (type_inst->opcode() != SpvOpTypeStruct) return false;
+  for (uint32_t in_operand_idx = kOpTypeStructInOperandMember;
+       in_operand_idx < type_inst->NumInOperands(); ++in_operand_idx) {
+    Instruction* member_type_inst = get_def_use_mgr()->GetDef(
+        type_inst->GetSingleWordInOperand(kOpTypeStructInOperandMember));
+    if (IsImageOrImagePtrType(member_type_inst)) return true;
+  }
+  return false;
+}
+
+bool ReplaceDescArrayAccessUsingVarIndex::IsConcreteType(
+    uint32_t type_id) const {
+  Instruction* type_inst = get_def_use_mgr()->GetDef(type_id);
+  if (type_inst->opcode() == SpvOpTypeInt ||
+      type_inst->opcode() == SpvOpTypeFloat) {
+    return true;
+  }
+  if (type_inst->opcode() == SpvOpTypeVector ||
+      type_inst->opcode() == SpvOpTypeMatrix ||
+      type_inst->opcode() == SpvOpTypeArray) {
+    return IsConcreteType(type_inst->GetSingleWordInOperand(0));
+  }
+  if (type_inst->opcode() == SpvOpTypeStruct) {
+    for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+      if (!IsConcreteType(type_inst->GetSingleWordInOperand(i))) return false;
+    }
+    return true;
+  }
+  return false;
+}
+
+BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateCaseBlock(
+    Instruction* access_chain, uint32_t element_index,
+    const std::deque<Instruction*>& insts_to_be_cloned,
+    uint32_t branch_target_id,
+    std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
+  auto* case_block = CreateNewBlock();
+  AddConstElementAccessToCaseBlock(case_block, access_chain, element_index,
+                                   old_ids_to_new_ids);
+  CloneInstsToBlock(case_block, access_chain, insts_to_be_cloned,
+                    old_ids_to_new_ids);
+  AddBranchToBlock(case_block, branch_target_id);
+  UseNewIdsInBlock(case_block, *old_ids_to_new_ids);
+  return case_block;
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::CloneInstsToBlock(
+    BasicBlock* block, Instruction* inst_to_skip_cloning,
+    const std::deque<Instruction*>& insts_to_be_cloned,
+    std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
+  for (auto* inst_to_be_cloned : insts_to_be_cloned) {
+    if (inst_to_be_cloned == inst_to_skip_cloning) continue;
+    std::unique_ptr<Instruction> clone(inst_to_be_cloned->Clone(context()));
+    if (inst_to_be_cloned->HasResultId()) {
+      uint32_t new_id = context()->TakeNextId();
+      clone->SetResultId(new_id);
+      (*old_ids_to_new_ids)[inst_to_be_cloned->result_id()] = new_id;
+    }
+    get_def_use_mgr()->AnalyzeInstDefUse(clone.get());
+    context()->set_instr_block(clone.get(), block);
+    block->AddInstruction(std::move(clone));
+  }
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::UseNewIdsInBlock(
+    BasicBlock* block,
+    const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const {
+  for (auto block_itr = block->begin(); block_itr != block->end();
+       ++block_itr) {
+    (&*block_itr)->ForEachInId([&old_ids_to_new_ids](uint32_t* idp) {
+      auto old_ids_to_new_ids_itr = old_ids_to_new_ids.find(*idp);
+      if (old_ids_to_new_ids_itr == old_ids_to_new_ids.end()) return;
+      *idp = old_ids_to_new_ids_itr->second;
+    });
+    get_def_use_mgr()->AnalyzeInstUse(&*block_itr);
+  }
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplaceNonUniformAccessWithSwitchCase(
+    Instruction* access_chain_final_user, Instruction* access_chain,
+    uint32_t number_of_elements,
+    const std::deque<Instruction*>& insts_to_be_cloned) const {
+  // Create merge block and add terminator
+  auto* block = context()->get_instr_block(access_chain_final_user);
+  auto* merge_block = SeparateInstructionsIntoNewBlock(
+      block, access_chain_final_user->NextNode());
+
+  auto* function = block->GetParent();
+
+  // Add case blocks
+  std::vector<uint32_t> phi_operands;
+  std::vector<uint32_t> case_block_ids;
+  for (uint32_t idx = 0; idx < number_of_elements; ++idx) {
+    std::unordered_map<uint32_t, uint32_t> old_ids_to_new_ids_for_cloned_insts;
+    std::unique_ptr<BasicBlock> case_block(CreateCaseBlock(
+        access_chain, idx, insts_to_be_cloned, merge_block->id(),
+        &old_ids_to_new_ids_for_cloned_insts));
+    case_block_ids.push_back(case_block->id());
+    function->InsertBasicBlockBefore(std::move(case_block), merge_block);
+
+    // Keep the operand for OpPhi
+    if (!access_chain_final_user->HasResultId()) continue;
+    uint32_t phi_operand =
+        GetValueWithKeyExistenceCheck(access_chain_final_user->result_id(),
+                                      old_ids_to_new_ids_for_cloned_insts);
+    phi_operands.push_back(phi_operand);
+  }
+
+  // Create default block
+  std::unique_ptr<BasicBlock> default_block(
+      CreateDefaultBlock(access_chain_final_user->HasResultId(), &phi_operands,
+                         merge_block->id()));
+  uint32_t default_block_id = default_block->id();
+  function->InsertBasicBlockBefore(std::move(default_block), merge_block);
+
+  // Create OpSwitch
+  uint32_t access_chain_index_var_id =
+      descsroautil::GetFirstIndexOfAccessChain(access_chain);
+  AddSwitchForAccessChain(block, access_chain_index_var_id, default_block_id,
+                          merge_block->id(), case_block_ids);
+
+  // Create phi instructions
+  if (!phi_operands.empty()) {
+    uint32_t phi_id = CreatePhiInstruction(merge_block, phi_operands,
+                                           case_block_ids, default_block_id);
+    context()->ReplaceAllUsesWith(access_chain_final_user->result_id(), phi_id);
+  }
+
+  // Replace OpPhi incoming block operand that uses |block| with |merge_block|
+  ReplacePhiIncomingBlock(block->id(), merge_block->id());
+}
+
+BasicBlock*
+ReplaceDescArrayAccessUsingVarIndex::SeparateInstructionsIntoNewBlock(
+    BasicBlock* block, Instruction* separation_begin_inst) const {
+  auto separation_begin = block->begin();
+  while (separation_begin != block->end() &&
+         &*separation_begin != separation_begin_inst) {
+    ++separation_begin;
+  }
+  return block->SplitBasicBlock(context(), context()->TakeNextId(),
+                                separation_begin);
+}
+
+BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateNewBlock() const {
+  auto* new_block = new BasicBlock(std::unique_ptr<Instruction>(
+      new Instruction(context(), SpvOpLabel, 0, context()->TakeNextId(), {})));
+  get_def_use_mgr()->AnalyzeInstDefUse(new_block->GetLabelInst());
+  context()->set_instr_block(new_block->GetLabelInst(), new_block);
+  return new_block;
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::UseConstIndexForAccessChain(
+    Instruction* access_chain, uint32_t const_element_idx) const {
+  uint32_t const_element_idx_id =
+      context()->get_constant_mgr()->GetUIntConst(const_element_idx);
+  access_chain->SetInOperand(kOpAccessChainInOperandIndexes,
+                             {const_element_idx_id});
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::AddConstElementAccessToCaseBlock(
+    BasicBlock* case_block, Instruction* access_chain,
+    uint32_t const_element_idx,
+    std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const {
+  std::unique_ptr<Instruction> access_clone(access_chain->Clone(context()));
+  UseConstIndexForAccessChain(access_clone.get(), const_element_idx);
+
+  uint32_t new_access_id = context()->TakeNextId();
+  (*old_ids_to_new_ids)[access_clone->result_id()] = new_access_id;
+  access_clone->SetResultId(new_access_id);
+  get_def_use_mgr()->AnalyzeInstDefUse(access_clone.get());
+
+  context()->set_instr_block(access_clone.get(), case_block);
+  case_block->AddInstruction(std::move(access_clone));
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::AddBranchToBlock(
+    BasicBlock* parent_block, uint32_t branch_destination) const {
+  InstructionBuilder builder{context(), parent_block,
+                             kAnalysisDefUseAndInstrToBlockMapping};
+  builder.AddBranch(branch_destination);
+}
+
+BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateDefaultBlock(
+    bool null_const_for_phi_is_needed, std::vector<uint32_t>* phi_operands,
+    uint32_t merge_block_id) const {
+  auto* default_block = CreateNewBlock();
+  AddBranchToBlock(default_block, merge_block_id);
+  if (!null_const_for_phi_is_needed) return default_block;
+
+  // Create null value for OpPhi
+  Instruction* inst = context()->get_def_use_mgr()->GetDef((*phi_operands)[0]);
+  auto* null_const_inst = GetConstNull(inst->type_id());
+  phi_operands->push_back(null_const_inst->result_id());
+  return default_block;
+}
+
+Instruction* ReplaceDescArrayAccessUsingVarIndex::GetConstNull(
+    uint32_t type_id) const {
+  assert(type_id != 0 && "Result type is expected");
+  auto* type = context()->get_type_mgr()->GetType(type_id);
+  auto* null_const = context()->get_constant_mgr()->GetConstant(type, {});
+  return context()->get_constant_mgr()->GetDefiningInstruction(null_const);
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::AddSwitchForAccessChain(
+    BasicBlock* parent_block, uint32_t access_chain_index_var_id,
+    uint32_t default_id, uint32_t merge_id,
+    const std::vector<uint32_t>& case_block_ids) const {
+  InstructionBuilder builder{context(), parent_block,
+                             kAnalysisDefUseAndInstrToBlockMapping};
+  std::vector<std::pair<Operand::OperandData, uint32_t>> cases;
+  for (uint32_t i = 0; i < static_cast<uint32_t>(case_block_ids.size()); ++i) {
+    cases.emplace_back(Operand::OperandData{i}, case_block_ids[i]);
+  }
+  builder.AddSwitch(access_chain_index_var_id, default_id, cases, merge_id);
+}
+
+uint32_t ReplaceDescArrayAccessUsingVarIndex::CreatePhiInstruction(
+    BasicBlock* parent_block, const std::vector<uint32_t>& phi_operands,
+    const std::vector<uint32_t>& case_block_ids,
+    uint32_t default_block_id) const {
+  std::vector<uint32_t> incomings;
+  assert(case_block_ids.size() + 1 == phi_operands.size() &&
+         "Number of Phi operands must be exactly 1 bigger than the one of case "
+         "blocks");
+  for (size_t i = 0; i < case_block_ids.size(); ++i) {
+    incomings.push_back(phi_operands[i]);
+    incomings.push_back(case_block_ids[i]);
+  }
+  incomings.push_back(phi_operands.back());
+  incomings.push_back(default_block_id);
+
+  InstructionBuilder builder{context(), &*parent_block->begin(),
+                             kAnalysisDefUseAndInstrToBlockMapping};
+  uint32_t phi_result_type_id =
+      context()->get_def_use_mgr()->GetDef(phi_operands[0])->type_id();
+  auto* phi = builder.AddPhi(phi_result_type_id, incomings);
+  return phi->result_id();
+}
+
+void ReplaceDescArrayAccessUsingVarIndex::ReplacePhiIncomingBlock(
+    uint32_t old_incoming_block_id, uint32_t new_incoming_block_id) const {
+  context()->ReplaceAllUsesWithPredicate(
+      old_incoming_block_id, new_incoming_block_id,
+      [](Instruction* use) { return use->opcode() == SpvOpPhi; });
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/replace_desc_array_access_using_var_index.h b/source/opt/replace_desc_array_access_using_var_index.h
new file mode 100644
index 0000000..e18222c
--- /dev/null
+++ b/source/opt/replace_desc_array_access_using_var_index.h
@@ -0,0 +1,204 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_
+#define SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_
+
+#include <cstdio>
+#include <memory>
+#include <queue>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "source/opt/function.h"
+#include "source/opt/pass.h"
+#include "source/opt/type_manager.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class ReplaceDescArrayAccessUsingVarIndex : public Pass {
+ public:
+  ReplaceDescArrayAccessUsingVarIndex() {}
+
+  const char* name() const override {
+    return "replace-desc-array-access-using-var-index";
+  }
+
+  Status Process() override;
+
+  IRContext::Analysis GetPreservedAnalyses() override {
+    return IRContext::kAnalysisDefUse |
+           IRContext::kAnalysisInstrToBlockMapping |
+           IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+  }
+
+ private:
+  // Replaces all acceses to |var| using variable indices with constant
+  // elements of the array |var|. Creates switch-case statements to determine
+  // the value of the variable index for all the possible cases. Returns
+  // whether replacement is done or not.
+  bool ReplaceVariableAccessesWithConstantElements(Instruction* var) const;
+
+  // Replaces the OpAccessChain or OpInBoundsAccessChain instruction |use| that
+  // uses the descriptor variable |var| with the OpAccessChain or
+  // OpInBoundsAccessChain instruction with a constant Indexes operand.
+  void ReplaceAccessChain(Instruction* var, Instruction* use) const;
+
+  // Updates the first Indexes operand of the OpAccessChain or
+  // OpInBoundsAccessChain instruction |access_chain| to let it use a constant
+  // index |const_element_idx|.
+  void UseConstIndexForAccessChain(Instruction* access_chain,
+                                   uint32_t const_element_idx) const;
+
+  // Replaces users of the OpAccessChain or OpInBoundsAccessChain instruction
+  // |access_chain| that accesses an array descriptor variable using variable
+  // indices with constant elements. |number_of_elements| is the number
+  // of array elements.
+  void ReplaceUsersOfAccessChain(Instruction* access_chain,
+                                 uint32_t number_of_elements) const;
+
+  // Puts all the recursive users of |access_chain| with concrete result types
+  // or the ones without result it in |final_users|.
+  void CollectRecursiveUsersWithConcreteType(
+      Instruction* access_chain, std::vector<Instruction*>* final_users) const;
+
+  // Recursively collects the operands of |user_of_image_insts| (and operands
+  // of the operands) whose result types are images/samplers or pointers/array/
+  // struct of them and returns them.
+  std::deque<Instruction*> CollectRequiredImageInsts(
+      Instruction* user_of_image_insts) const;
+
+  // Returns whether result type of |inst| is an image/sampler/pointer of image
+  // or sampler or not.
+  bool HasImageOrImagePtrType(const Instruction* inst) const;
+
+  // Returns whether |type_inst| is an image/sampler or pointer/array/struct of
+  // image or sampler or not.
+  bool IsImageOrImagePtrType(const Instruction* type_inst) const;
+
+  // Returns whether the type with |type_id| is a concrete type or not.
+  bool IsConcreteType(uint32_t type_id) const;
+
+  // Replaces the non-uniform access to a descriptor variable
+  // |access_chain_final_user| with OpSwitch instruction and case blocks. Each
+  // case block will contain a clone of |access_chain| and clones of
+  // |non_uniform_accesses_to_clone| that are recursively used by
+  // |access_chain_final_user|. The clone of |access_chain| (or
+  // OpInBoundsAccessChain) will have a constant index for its first index. The
+  // OpSwitch instruction will have the cases for the variable index of
+  // |access_chain| from 0 to |number_of_elements| - 1.
+  void ReplaceNonUniformAccessWithSwitchCase(
+      Instruction* access_chain_final_user, Instruction* access_chain,
+      uint32_t number_of_elements,
+      const std::deque<Instruction*>& non_uniform_accesses_to_clone) const;
+
+  // Creates and returns a new basic block that contains all instructions of
+  // |block| after |separation_begin_inst|. The new basic block is added to the
+  // function in this method.
+  BasicBlock* SeparateInstructionsIntoNewBlock(
+      BasicBlock* block, Instruction* separation_begin_inst) const;
+
+  // Creates and returns a new block.
+  BasicBlock* CreateNewBlock() const;
+
+  // Returns the first operand id of the OpAccessChain or OpInBoundsAccessChain
+  // instruction |access_chain|.
+  uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) const;
+
+  // Adds a clone of the OpAccessChain or OpInBoundsAccessChain instruction
+  // |access_chain| to |case_block|. The clone of |access_chain| will use
+  // |const_element_idx| for its first index. |old_ids_to_new_ids| keeps the
+  // mapping from the result id of |access_chain| to the result of its clone.
+  void AddConstElementAccessToCaseBlock(
+      BasicBlock* case_block, Instruction* access_chain,
+      uint32_t const_element_idx,
+      std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
+
+  // Clones all instructions in |insts_to_be_cloned| and put them to |block|.
+  // |old_ids_to_new_ids| keeps the mapping from the result id of each
+  // instruction of |insts_to_be_cloned| to the result of their clones.
+  void CloneInstsToBlock(
+      BasicBlock* block, Instruction* inst_to_skip_cloning,
+      const std::deque<Instruction*>& insts_to_be_cloned,
+      std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
+
+  // Adds OpBranch to |branch_destination| at the end of |parent_block|.
+  void AddBranchToBlock(BasicBlock* parent_block,
+                        uint32_t branch_destination) const;
+
+  // Replaces in-operands of all instructions in the basic block |block| using
+  // |old_ids_to_new_ids|. It conducts the replacement only if the in-operand
+  // id is a key of |old_ids_to_new_ids|.
+  void UseNewIdsInBlock(
+      BasicBlock* block,
+      const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const;
+
+  // Creates a case block for |element_index| case. It adds clones of
+  // |insts_to_be_cloned| and a clone of |access_chain| with |element_index| as
+  // its first index. The termination instruction of the created case block will
+  // be a branch to |branch_target_id|. Puts old ids to new ids map for the
+  // cloned instructions in |old_ids_to_new_ids|.
+  BasicBlock* CreateCaseBlock(
+      Instruction* access_chain, uint32_t element_index,
+      const std::deque<Instruction*>& insts_to_be_cloned,
+      uint32_t branch_target_id,
+      std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const;
+
+  // Creates a default block for switch-case statement that has only a single
+  // instruction OpBranch whose target is a basic block with |merge_block_id|.
+  // If |null_const_for_phi_is_needed| is true, gets or creates a default null
+  // constant value for a phi instruction whose operands are |phi_operands| and
+  // puts it in |phi_operands|.
+  BasicBlock* CreateDefaultBlock(bool null_const_for_phi_is_needed,
+                                 std::vector<uint32_t>* phi_operands,
+                                 uint32_t merge_block_id) const;
+
+  // Creates and adds an OpSwitch used for the selection of OpAccessChain whose
+  // first Indexes operand is |access_chain_index_var_id|. The OpSwitch will be
+  // added at the end of |parent_block|. It will jump to |default_id| for the
+  // default case and jumps to one of case blocks whoes ids are |case_block_ids|
+  // if |access_chain_index_var_id| matches the case number. |merge_id| is the
+  // merge block id.
+  void AddSwitchForAccessChain(
+      BasicBlock* parent_block, uint32_t access_chain_index_var_id,
+      uint32_t default_id, uint32_t merge_id,
+      const std::vector<uint32_t>& case_block_ids) const;
+
+  // Creates a phi instruction with |phi_operands| as values and
+  // |case_block_ids| and |default_block_id| as incoming blocks. The size of
+  // |phi_operands| must be exactly 1 larger than the size of |case_block_ids|.
+  // The last element of |phi_operands| will be used for |default_block_id|. It
+  // adds the phi instruction to the beginning of |parent_block|.
+  uint32_t CreatePhiInstruction(BasicBlock* parent_block,
+                                const std::vector<uint32_t>& phi_operands,
+                                const std::vector<uint32_t>& case_block_ids,
+                                uint32_t default_block_id) const;
+
+  // Replaces the incoming block operand of OpPhi instructions with
+  // |new_incoming_block_id| if the incoming block operand is
+  // |old_incoming_block_id|.
+  void ReplacePhiIncomingBlock(uint32_t old_incoming_block_id,
+                               uint32_t new_incoming_block_id) const;
+
+  // Create an OpConstantNull instruction whose result type id is |type_id|.
+  Instruction* GetConstNull(uint32_t type_id) const;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_
diff --git a/source/opt/replace_invalid_opc.cpp b/source/opt/replace_invalid_opc.cpp
index 38b7539..e3b9d3e 100644
--- a/source/opt/replace_invalid_opc.cpp
+++ b/source/opt/replace_invalid_opc.cpp
@@ -71,10 +71,10 @@
   function->ForEachInst(
       [model, &modified, &last_line_dbg_inst, this](Instruction* inst) {
         // Track the debug information so we can have a meaningful message.
-        if (inst->opcode() == SpvOpLabel || inst->opcode() == SpvOpNoLine) {
+        if (inst->opcode() == SpvOpLabel || inst->IsNoLine()) {
           last_line_dbg_inst = nullptr;
           return;
-        } else if (inst->opcode() == SpvOpLine) {
+        } else if (inst->IsLine()) {
           last_line_dbg_inst = inst;
           return;
         }
@@ -100,8 +100,18 @@
             ReplaceInstruction(inst, nullptr, 0, 0);
           } else {
             // Get the name of the source file.
-            Instruction* file_name = context()->get_def_use_mgr()->GetDef(
-                last_line_dbg_inst->GetSingleWordInOperand(0));
+            uint32_t file_name_id = 0;
+            if (last_line_dbg_inst->opcode() == SpvOpLine) {
+              file_name_id = last_line_dbg_inst->GetSingleWordInOperand(0);
+            } else {  // Shader100::DebugLine
+              uint32_t debug_source_id =
+                  last_line_dbg_inst->GetSingleWordInOperand(2);
+              Instruction* debug_source_inst =
+                  context()->get_def_use_mgr()->GetDef(debug_source_id);
+              file_name_id = debug_source_inst->GetSingleWordInOperand(2);
+            }
+            Instruction* file_name =
+                context()->get_def_use_mgr()->GetDef(file_name_id);
             const char* source = reinterpret_cast<const char*>(
                 &file_name->GetInOperand(0).words[0]);
 
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index c8e0da5..4d6a7aa 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -27,6 +27,7 @@
 
 static const uint32_t kDebugValueOperandValueIndex = 5;
 static const uint32_t kDebugValueOperandExpressionIndex = 6;
+static const uint32_t kDebugDeclareOperandVariableIndex = 5;
 
 namespace spvtools {
 namespace opt {
@@ -34,6 +35,10 @@
 Pass::Status ScalarReplacementPass::Process() {
   Status status = Status::SuccessWithoutChange;
   for (auto& f : *get_module()) {
+    if (f.IsDeclaration()) {
+      continue;
+    }
+
     Status functionStatus = ProcessFunction(&f);
     if (functionStatus == Status::Failure)
       return functionStatus;
@@ -83,14 +88,14 @@
   std::vector<Instruction*> dead;
   bool replaced_all_uses = get_def_use_mgr()->WhileEachUser(
       inst, [this, &replacements, &dead](Instruction* user) {
-        if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) {
+        if (user->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
           if (ReplaceWholeDebugDeclare(user, replacements)) {
             dead.push_back(user);
             return true;
           }
           return false;
         }
-        if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue) {
+        if (user->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) {
           if (ReplaceWholeDebugValue(user, replacements)) {
             dead.push_back(user);
             return true;
@@ -172,10 +177,15 @@
   // Add DebugValue instruction with Indexes operand and Deref operation.
   int32_t idx = 0;
   for (const auto* var : replacements) {
+    Instruction* insert_before = var->NextNode();
+    while (insert_before->opcode() == SpvOpVariable)
+      insert_before = insert_before->NextNode();
+    assert(insert_before != nullptr && "unexpected end of list");
     Instruction* added_dbg_value =
         context()->get_debug_info_mgr()->AddDebugValueForDecl(
             dbg_decl, /*value_id=*/var->result_id(),
-            /*insert_before=*/var->NextNode());
+            /*insert_before=*/insert_before, /*scope_and_line=*/dbg_decl);
+
     if (added_dbg_value == nullptr) return false;
     added_dbg_value->AddOperand(
         {SPV_OPERAND_TYPE_ID,
@@ -503,7 +513,7 @@
     }
   }
 
-  // Update the OpenCL.DebugInfo.100 debug information.
+  // Update the DebugInfo debug information.
   inst->UpdateDebugInfoFrom(varInst);
 
   replacements->push_back(inst);
@@ -790,8 +800,8 @@
   get_def_use_mgr()->ForEachUse(inst, [this, max_legal_index, stats, &ok](
                                           const Instruction* user,
                                           uint32_t index) {
-    if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare ||
-        user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue) {
+    if (user->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare ||
+        user->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) {
       // TODO: include num_partial_accesses if it uses Fragment operation or
       // DebugValue has Indexes operand.
       stats->num_full_accesses++;
@@ -860,6 +870,14 @@
           case SpvOpStore:
             if (!CheckStore(user, index)) ok = false;
             break;
+          case SpvOpImageTexelPointer:
+            if (!CheckImageTexelPointer(index)) ok = false;
+            break;
+          case SpvOpExtInst:
+            if (user->GetCommonDebugOpcode() != CommonDebugInfoDebugDeclare ||
+                !CheckDebugDeclare(index))
+              ok = false;
+            break;
           default:
             ok = false;
             break;
@@ -869,6 +887,10 @@
   return ok;
 }
 
+bool ScalarReplacementPass::CheckImageTexelPointer(uint32_t index) const {
+  return index == 2u;
+}
+
 bool ScalarReplacementPass::CheckLoad(const Instruction* inst,
                                       uint32_t index) const {
   if (index != 2u) return false;
@@ -886,6 +908,12 @@
     return false;
   return true;
 }
+
+bool ScalarReplacementPass::CheckDebugDeclare(uint32_t index) const {
+  if (index != kDebugDeclareOperandVariableIndex) return false;
+  return true;
+}
+
 bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const {
   if (max_num_elements_ == 0) {
     return false;
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index 1f6c928..0928830 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -142,6 +142,13 @@
   // of |inst| and the store is not to volatile memory.
   bool CheckStore(const Instruction* inst, uint32_t index) const;
 
+  // Returns true if the DebugDeclare can be scalarized at |index|.
+  bool CheckDebugDeclare(uint32_t index) const;
+
+  // Returns true if |index| is the pointer operand of an OpImageTexelPointer
+  // instruction.
+  bool CheckImageTexelPointer(uint32_t index) const;
+
   // Creates a variable of type |typeId| from the |index|'th element of
   // |varInst|. The new variable is added to |replacements|.  If the variable
   // could not be created, then |nullptr| is appended to |replacements|.
diff --git a/source/opt/set_spec_constant_default_value_pass.cpp b/source/opt/set_spec_constant_default_value_pass.cpp
index 4c8d116..4def2b0 100644
--- a/source/opt/set_spec_constant_default_value_pass.cpp
+++ b/source/opt/set_spec_constant_default_value_pass.cpp
@@ -85,6 +85,10 @@
 //   with 0x1, which represents a 'true'.
 //   If all words in the bit pattern are zero, returns a bit pattern with 0x0,
 //   which represents a 'false'.
+// For integer and floating point types narrower than 32 bits, the upper bits
+// in the input bit pattern are ignored.  Instead the upper bits are set
+// according to SPIR-V literal requirements: sign extend a signed integer, and
+// otherwise set the upper bits to zero.
 std::vector<uint32_t> ParseDefaultValueBitPattern(
     const std::vector<uint32_t>& input_bit_pattern,
     const analysis::Type* type) {
@@ -98,12 +102,33 @@
     }
     return result;
   } else if (const auto* IT = type->AsInteger()) {
-    if (IT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
-      return std::vector<uint32_t>(input_bit_pattern);
+    const auto width = IT->width();
+    assert(width > 0);
+    const auto adjusted_width = std::max(32u, width);
+    if (adjusted_width == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
+      result = std::vector<uint32_t>(input_bit_pattern);
+      if (width < 32) {
+        const uint32_t high_active_bit = (1u << width) >> 1;
+        if (IT->IsSigned() && (high_active_bit & result[0])) {
+          // Sign extend.  This overwrites the sign bit again, but that's ok.
+          result[0] = result[0] | ~(high_active_bit - 1);
+        } else {
+          // Upper bits must be zero.
+          result[0] = result[0] & ((1u << width) - 1);
+        }
+      }
+      return result;
     }
   } else if (const auto* FT = type->AsFloat()) {
-    if (FT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
-      return std::vector<uint32_t>(input_bit_pattern);
+    const auto width = FT->width();
+    const auto adjusted_width = std::max(32u, width);
+    if (adjusted_width == input_bit_pattern.size() * sizeof(uint32_t) * 8) {
+      result = std::vector<uint32_t>(input_bit_pattern);
+      if (width < 32) {
+        // Upper bits must be zero.
+        result[0] = result[0] & ((1u << width) - 1);
+      }
+      return result;
     }
   }
   result.clear();
diff --git a/source/opt/simplification_pass.cpp b/source/opt/simplification_pass.cpp
index 319ceec..43ec15f 100644
--- a/source/opt/simplification_pass.cpp
+++ b/source/opt/simplification_pass.cpp
@@ -45,6 +45,10 @@
 }
 
 bool SimplificationPass::SimplifyFunction(Function* function) {
+  if (function->IsDeclaration()) {
+    return false;
+  }
+
   bool modified = false;
   // Phase 1: Traverse all instructions in dominance order.
   // The second phase will only be on the instructions whose inputs have changed
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 0489f03..29ab612 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -658,8 +658,8 @@
   //   a = 3;
   //   foo(3);
   // After inlining:
-  //   a = 3; // we want to specify "DebugValue: %x = %int_3"
-  //   foo and x disappeared!
+  //   a = 3;
+  //   foo and x disappeared but we want to specify "DebugValue: %x = %int_3".
   //
   // We want to specify the value for the variable using |defs_at_block_[bb]|,
   // where |bb| is the basic block contains the decl.
@@ -681,16 +681,17 @@
     if (value && (pass_->context()->get_instr_block(value) == nullptr ||
                   dom_tree->Dominates(value, decl))) {
       if (pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
-              decl, value->result_id(), decl) == nullptr) {
+              decl, value->result_id(), decl, value) == nullptr) {
         return Pass::Status::Failure;
       }
     } else {
       // If |value| in the same basic block does not dominate |decl|, we can
       // assign the value in the immediate dominator.
       value_id = GetValueAtBlock(var_id, dom_tree->ImmediateDominator(bb));
+      if (value_id) value = pass_->get_def_use_mgr()->GetDef(value_id);
       if (value_id &&
           pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
-              decl, value_id, decl) == nullptr) {
+              decl, value_id, decl, value) == nullptr) {
         return Pass::Status::Failure;
       }
     }
@@ -752,6 +753,9 @@
 Pass::Status SSARewritePass::Process() {
   Status status = Status::SuccessWithoutChange;
   for (auto& fn : *get_module()) {
+    if (fn.IsDeclaration()) {
+      continue;
+    }
     status =
         CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn));
     // Kill DebugDeclares for target variables.
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index ce9c2c1..7935ad3 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -223,7 +223,7 @@
   case Type::k##kind:                                                     \
     typeInst = MakeUnique<Instruction>(context(), SpvOpType##kind, 0, id, \
                                        std::initializer_list<Operand>{}); \
-    break;
+    break
     DefineParameterlessCase(Void);
     DefineParameterlessCase(Bool);
     DefineParameterlessCase(Sampler);
@@ -513,7 +513,7 @@
 #define DefineNoSubtypeCase(kind)             \
   case Type::k##kind:                         \
     rebuilt_ty.reset(type.Clone().release()); \
-    return type_pool_.insert(std::move(rebuilt_ty)).first->get();
+    return type_pool_.insert(std::move(rebuilt_ty)).first->get()
 
     DefineNoSubtypeCase(Void);
     DefineNoSubtypeCase(Bool);
diff --git a/source/opt/types.h b/source/opt/types.h
index d5be9be..9ecd41a 100644
--- a/source/opt/types.h
+++ b/source/opt/types.h
@@ -101,7 +101,7 @@
 
   Type(Kind k) : kind_(k) {}
 
-  virtual ~Type() {}
+  virtual ~Type() = default;
 
   // Attaches a decoration directly on this type.
   void AddDecoration(std::vector<uint32_t>&& d) {
diff --git a/source/opt/value_number_table.cpp b/source/opt/value_number_table.cpp
index 32d6de9..5271e3f 100644
--- a/source/opt/value_number_table.cpp
+++ b/source/opt/value_number_table.cpp
@@ -50,7 +50,7 @@
   // OpSampledImage and OpImage must remain in the same basic block in which
   // they are used, because of this we will assign each one it own value number.
   if (!context()->IsCombinatorInstruction(inst) &&
-      !inst->IsOpenCL100DebugInstr()) {
+      !inst->IsCommonDebugInstr()) {
     value = TakeNextValueNumber();
     id_to_value_[inst->result_id()] = value;
     return value;
diff --git a/source/opt/vector_dce.cpp b/source/opt/vector_dce.cpp
index 14a55c1..28d94a0 100644
--- a/source/opt/vector_dce.cpp
+++ b/source/opt/vector_dce.cpp
@@ -52,7 +52,7 @@
   // components are live because of arbitrary nesting of structs.
   function->ForEachInst(
       [&work_list, this, live_components](Instruction* current_inst) {
-        if (current_inst->IsOpenCL100DebugInstr()) {
+        if (current_inst->IsCommonDebugInstr()) {
           return;
         }
         if (!HasVectorOrScalarResult(current_inst) ||
@@ -110,7 +110,11 @@
     if (current_inst->NumInOperands() < 2) {
       new_item.components = live_elements;
     } else {
-      new_item.components.Set(current_inst->GetSingleWordInOperand(1));
+      uint32_t element_index = current_inst->GetSingleWordInOperand(1);
+      uint32_t item_size = GetVectorComponentCount(operand_inst->type_id());
+      if (element_index < item_size) {
+        new_item.components.Set(element_index);
+      }
     }
     AddItemToWorkListIfNeeded(new_item, live_components, work_list);
   }
@@ -176,10 +180,10 @@
   second_operand.instruction =
       def_use_mgr->GetDef(current_item.instruction->GetSingleWordInOperand(1));
 
-  analysis::TypeManager* type_mgr = context()->get_type_mgr();
-  analysis::Vector* first_type =
-      type_mgr->GetType(first_operand.instruction->type_id())->AsVector();
-  uint32_t size_of_first_operand = first_type->element_count();
+  uint32_t size_of_first_operand =
+      GetVectorComponentCount(first_operand.instruction->type_id());
+  uint32_t size_of_second_operand =
+      GetVectorComponentCount(second_operand.instruction->type_id());
 
   for (uint32_t in_op = 2; in_op < current_item.instruction->NumInOperands();
        ++in_op) {
@@ -187,7 +191,7 @@
     if (current_item.components.Get(in_op - 2)) {
       if (index < size_of_first_operand) {
         first_operand.components.Set(index);
-      } else {
+      } else if (index - size_of_first_operand < size_of_second_operand) {
         second_operand.components.Set(index - size_of_first_operand);
       }
     }
@@ -202,7 +206,6 @@
     VectorDCE::LiveComponentMap* live_components,
     std::vector<VectorDCE::WorkListItem>* work_list) {
   analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
-  analysis::TypeManager* type_mgr = context()->get_type_mgr();
 
   uint32_t current_component = 0;
   Instruction* current_inst = work_item.instruction;
@@ -223,8 +226,7 @@
       assert(HasVectorResult(op_inst));
       WorkListItem new_work_item;
       new_work_item.instruction = op_inst;
-      uint32_t op_vector_size =
-          type_mgr->GetType(op_inst->type_id())->AsVector()->element_count();
+      uint32_t op_vector_size = GetVectorComponentCount(op_inst->type_id());
 
       for (uint32_t op_vector_idx = 0; op_vector_idx < op_vector_size;
            op_vector_idx++, current_component++) {
@@ -297,6 +299,18 @@
   }
 }
 
+uint32_t VectorDCE::GetVectorComponentCount(uint32_t type_id) {
+  assert(type_id != 0 &&
+         "Trying to get the vector element count, but the type id is 0");
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  const analysis::Type* type = type_mgr->GetType(type_id);
+  const analysis::Vector* vector_type = type->AsVector();
+  assert(
+      vector_type &&
+      "Trying to get the vector element count, but the type is not a vector");
+  return vector_type->element_count();
+}
+
 bool VectorDCE::RewriteInstructions(
     Function* function, const VectorDCE::LiveComponentMap& live_components) {
   bool modified = false;
@@ -394,7 +408,7 @@
     Instruction* composite, std::vector<Instruction*>* dead_dbg_value) {
   context()->get_def_use_mgr()->ForEachUser(
       composite, [&dead_dbg_value](Instruction* use) {
-        if (use->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue)
+        if (use->GetCommonDebugOpcode() == CommonDebugInfoDebugValue)
           dead_dbg_value->push_back(use);
       });
 }
diff --git a/source/opt/vector_dce.h b/source/opt/vector_dce.h
index 0df9aee..4d30b92 100644
--- a/source/opt/vector_dce.h
+++ b/source/opt/vector_dce.h
@@ -94,12 +94,15 @@
   // Returns true if the result of |inst| is a vector or a scalar.
   bool HasVectorOrScalarResult(const Instruction* inst) const;
 
-  // Returns true if the result of |inst| is a scalar.
+  // Returns true if the result of |inst| is a vector.
   bool HasVectorResult(const Instruction* inst) const;
 
-  // Returns true if the result of |inst| is a vector.
+  // Returns true if the result of |inst| is a scalar.
   bool HasScalarResult(const Instruction* inst) const;
 
+  // Returns the number of elements in the vector type with id |type_id|.
+  uint32_t GetVectorComponentCount(uint32_t type_id);
+
   // Adds |work_item| to |work_list| if it is not already live according to
   // |live_components|.  |live_components| is updated to indicate that
   // |work_item| is now live.
diff --git a/source/print.cpp b/source/print.cpp
index 128587a..2418c5b 100644
--- a/source/print.cpp
+++ b/source/print.cpp
@@ -15,7 +15,7 @@
 #include "source/print.h"
 
 #if defined(SPIRV_ANDROID) || defined(SPIRV_LINUX) || defined(SPIRV_MAC) || \
-    defined(SPIRV_IOS) || defined(SPIRV_FREEBSD) ||                         \
+    defined(SPIRV_IOS) || defined(SPIRV_TVOS) || defined(SPIRV_FREEBSD) ||  \
     defined(SPIRV_EMSCRIPTEN) || defined(SPIRV_FUCHSIA)
 namespace spvtools {
 
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index a3291c7..6fd8409 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -14,6 +14,8 @@
 set(SPIRV_TOOLS_REDUCE_SOURCES
         change_operand_reduction_opportunity.h
         change_operand_to_undef_reduction_opportunity.h
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.h
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.h
         merge_blocks_reduction_opportunity.h
         merge_blocks_reduction_opportunity_finder.h
         operand_to_const_reduction_opportunity_finder.h
@@ -34,15 +36,17 @@
         remove_struct_member_reduction_opportunity.h
         remove_unused_instruction_reduction_opportunity_finder.h
         remove_unused_struct_member_reduction_opportunity_finder.h
-        structured_loop_to_selection_reduction_opportunity.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
+        structured_construct_to_block_reduction_opportunity.h
+        structured_construct_to_block_reduction_opportunity_finder.h
+        structured_loop_to_selection_reduction_opportunity.h
+        structured_loop_to_selection_reduction_opportunity_finder.h
 
         change_operand_reduction_opportunity.cpp
         change_operand_to_undef_reduction_opportunity.cpp
+        conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
+        conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
         merge_blocks_reduction_opportunity.cpp
         merge_blocks_reduction_opportunity_finder.cpp
         operand_to_const_reduction_opportunity_finder.cpp
@@ -63,12 +67,12 @@
         remove_struct_member_reduction_opportunity.cpp
         remove_unused_instruction_reduction_opportunity_finder.cpp
         remove_unused_struct_member_reduction_opportunity_finder.cpp
-        structured_loop_to_selection_reduction_opportunity.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
+        structured_construct_to_block_reduction_opportunity.cpp
+        structured_construct_to_block_reduction_opportunity_finder.cpp
+        structured_loop_to_selection_reduction_opportunity.cpp
+        structured_loop_to_selection_reduction_opportunity_finder.cpp
 )
 
 if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
diff --git a/source/reduce/change_operand_reduction_opportunity.cpp b/source/reduce/change_operand_reduction_opportunity.cpp
index c3f6fd7..18e340b 100644
--- a/source/reduce/change_operand_reduction_opportunity.cpp
+++ b/source/reduce/change_operand_reduction_opportunity.cpp
@@ -14,6 +14,8 @@
 
 #include "source/reduce/change_operand_reduction_opportunity.h"
 
+#include "source/opt/ir_context.h"
+
 namespace spvtools {
 namespace reduce {
 
@@ -26,6 +28,7 @@
 
 void ChangeOperandReductionOpportunity::Apply() {
   inst_->SetOperand(operand_index_, {new_id_});
+  inst_->context()->get_def_use_mgr()->UpdateDefUse(inst_);
 }
 
 }  // namespace reduce
diff --git a/source/reduce/change_operand_to_undef_reduction_opportunity.cpp b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
index 8e33da6..7cc06a0 100644
--- a/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
+++ b/source/reduce/change_operand_to_undef_reduction_opportunity.cpp
@@ -35,6 +35,7 @@
   assert(operand_type_id);
   auto undef_id = FindOrCreateGlobalUndef(context_, operand_type_id);
   inst_->SetOperand(operand_index_, {undef_id});
+  context_->InvalidateAnalyses(opt::IRContext::kAnalysisDefUse);
 }
 
 }  // namespace reduce
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 18eeaeb..b752f41 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -28,6 +28,7 @@
 #include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
 #include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
 #include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_construct_to_block_reduction_opportunity_finder.h"
 #include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
 #include "source/spirv_reducer_options.h"
 
@@ -54,10 +55,10 @@
 }
 
 Reducer::ReductionResultStatus Reducer::Run(
-    std::vector<uint32_t>&& binary_in, std::vector<uint32_t>* binary_out,
+    const std::vector<uint32_t>& binary_in, std::vector<uint32_t>* binary_out,
     spv_const_reducer_options options,
     spv_validator_options validator_options) {
-  std::vector<uint32_t> current_binary(std::move(binary_in));
+  std::vector<uint32_t> current_binary(binary_in);
 
   spvtools::SpirvTools tools(target_env_);
   assert(tools.IsValid() && "Failed to create SPIRV-Tools interface");
@@ -113,6 +114,8 @@
   AddReductionPass(
       spvtools::MakeUnique<OperandToDominatingIdReductionOpportunityFinder>());
   AddReductionPass(spvtools::MakeUnique<
+                   StructuredConstructToBlockReductionOpportunityFinder>());
+  AddReductionPass(spvtools::MakeUnique<
                    StructuredLoopToSelectionReductionOpportunityFinder>());
   AddReductionPass(
       spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
@@ -138,13 +141,13 @@
 }
 
 void Reducer::AddReductionPass(
-    std::unique_ptr<ReductionOpportunityFinder>&& finder) {
+    std::unique_ptr<ReductionOpportunityFinder> finder) {
   passes_.push_back(
       spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
 }
 
 void Reducer::AddCleanupReductionPass(
-    std::unique_ptr<ReductionOpportunityFinder>&& finder) {
+    std::unique_ptr<ReductionOpportunityFinder> finder) {
   cleanup_passes_.push_back(
       spvtools::MakeUnique<ReductionPass>(target_env_, std::move(finder)));
 }
diff --git a/source/reduce/reducer.h b/source/reduce/reducer.h
index 864ce75..f3ba180 100644
--- a/source/reduce/reducer.h
+++ b/source/reduce/reducer.h
@@ -84,17 +84,17 @@
 
   // 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);
+  void AddReductionPass(std::unique_ptr<ReductionOpportunityFinder> finder);
 
   // Adds a cleanup reduction pass based on the given finder to the sequence of
   // passes that will run after other passes.
   void AddCleanupReductionPass(
-      std::unique_ptr<ReductionOpportunityFinder>&& finder);
+      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,
+  ReductionResultStatus Run(const std::vector<uint32_t>& binary_in,
                             std::vector<uint32_t>* binary_out,
                             spv_const_reducer_options options,
                             spv_validator_options validator_options);
diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp
index aa48105..55e9576 100644
--- a/source/reduce/remove_block_reduction_opportunity.cpp
+++ b/source/reduce/remove_block_reduction_opportunity.cpp
@@ -20,12 +20,11 @@
 namespace reduce {
 
 RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
-    opt::Function* function, opt::BasicBlock* block)
-    : function_(function), block_(block) {
+    opt::IRContext* context, opt::Function* function, opt::BasicBlock* block)
+    : context_(context), function_(function), block_(block) {
   // precondition:
   assert(block_->begin() != block_->end() &&
-         block_->begin()->context()->get_def_use_mgr()->NumUsers(
-             block_->id()) == 0 &&
+         context_->get_def_use_mgr()->NumUsers(block_->id()) == 0 &&
          "RemoveBlockReductionOpportunity block must have 0 references");
 }
 
@@ -38,10 +37,8 @@
   // 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.
+      context_->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
       return;
     }
   }
diff --git a/source/reduce/remove_block_reduction_opportunity.h b/source/reduce/remove_block_reduction_opportunity.h
index 4b358ab..03fede5 100644
--- a/source/reduce/remove_block_reduction_opportunity.h
+++ b/source/reduce/remove_block_reduction_opportunity.h
@@ -27,7 +27,8 @@
 class RemoveBlockReductionOpportunity : public ReductionOpportunity {
  public:
   // Creates the opportunity to remove |block| in |function| in |context|.
-  RemoveBlockReductionOpportunity(opt::Function* function,
+  RemoveBlockReductionOpportunity(opt::IRContext* context,
+                                  opt::Function* function,
                                   opt::BasicBlock* block);
 
   bool PreconditionHolds() override;
@@ -36,6 +37,7 @@
   void Apply() override;
 
  private:
+  opt::IRContext* context_;
   opt::Function* function_;
   opt::BasicBlock* block_;
 };
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp
index 27a4570..3b13728 100644
--- a/source/reduce/remove_block_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp
@@ -32,8 +32,8 @@
   for (auto* function : GetTargetFunctions(context, target_function)) {
     for (auto bi = function->begin(); bi != function->end(); ++bi) {
       if (IsBlockValidOpportunity(context, function, &bi)) {
-        result.push_back(
-            MakeUnique<RemoveBlockReductionOpportunity>(function, &*bi));
+        result.push_back(MakeUnique<RemoveBlockReductionOpportunity>(
+            context, function, &*bi));
       }
     }
   }
diff --git a/source/reduce/remove_function_reduction_opportunity.cpp b/source/reduce/remove_function_reduction_opportunity.cpp
index ecad670..4b85058 100644
--- a/source/reduce/remove_function_reduction_opportunity.cpp
+++ b/source/reduce/remove_function_reduction_opportunity.cpp
@@ -29,8 +29,8 @@
   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);
+      function_it.Erase();
+      context_->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
       return;
     }
   }
diff --git a/source/reduce/remove_struct_member_reduction_opportunity.cpp b/source/reduce/remove_struct_member_reduction_opportunity.cpp
index 787c629..da096e1 100644
--- a/source/reduce/remove_struct_member_reduction_opportunity.cpp
+++ b/source/reduce/remove_struct_member_reduction_opportunity.cpp
@@ -129,6 +129,8 @@
 
   // Remove the member from the struct type.
   struct_type_->RemoveInOperand(member_index_);
+
+  context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 }
 
 void RemoveStructMemberReductionOpportunity::AdjustAccessedIndices(
diff --git a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
index ca17f9e..a2be0c4 100644
--- a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
@@ -51,6 +51,8 @@
       {{SPV_OPERAND_TYPE_ID,
         {conditional_branch_instruction_->GetSingleWordInOperand(
             kTrueBranchOperandIndex)}}});
+  conditional_branch_instruction_->context()->InvalidateAnalysesExceptFor(
+      opt::IRContext::kAnalysisNone);
 }
 
 }  // namespace reduce
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity.cpp b/source/reduce/structured_construct_to_block_reduction_opportunity.cpp
new file mode 100644
index 0000000..ed73841
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity.cpp
@@ -0,0 +1,67 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/structured_construct_to_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+bool StructuredConstructToBlockReductionOpportunity::PreconditionHolds() {
+  return context_->get_def_use_mgr()->GetDef(construct_header_) != nullptr;
+}
+
+void StructuredConstructToBlockReductionOpportunity::Apply() {
+  auto header_block = context_->cfg()->block(construct_header_);
+  auto merge_block = context_->cfg()->block(header_block->MergeBlockId());
+
+  auto* enclosing_function = header_block->GetParent();
+
+  // A region of blocks is defined in terms of dominators and post-dominators,
+  // so we compute these for the enclosing function.
+  auto* dominators = context_->GetDominatorAnalysis(enclosing_function);
+  auto* postdominators = context_->GetPostDominatorAnalysis(enclosing_function);
+
+  // For each block in the function, determine whether it is inside the region.
+  // If it is, delete it.
+  for (auto block_it = enclosing_function->begin();
+       block_it != enclosing_function->end();) {
+    if (header_block != &*block_it && merge_block != &*block_it &&
+        dominators->Dominates(header_block, &*block_it) &&
+        postdominators->Dominates(merge_block, &*block_it)) {
+      block_it = block_it.Erase();
+    } else {
+      ++block_it;
+    }
+  }
+  // Having removed some blocks from the module it is necessary to invalidate
+  // analyses, since the remaining patch-up work depends on various analyses
+  // which will otherwise reference blocks that have been deleted.
+  context_->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // We demote the header of the region to a regular block by deleting its merge
+  // instruction.
+  context_->KillInst(header_block->GetMergeInst());
+
+  // The terminator for the header block is changed to be an unconditional
+  // branch to the merge block.
+  header_block->terminator()->SetOpcode(SpvOpBranch);
+  header_block->terminator()->SetInOperands(
+      {{SPV_OPERAND_TYPE_ID, {merge_block->id()}}});
+
+  // This is an intrusive change, so we invalidate all analyses.
+  context_->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity.h b/source/reduce/structured_construct_to_block_reduction_opportunity.h
new file mode 100644
index 0000000..f461a2f
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_H_
+
+#include "source/opt/ir_context.h"
+#include "source/reduce/reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+// An opportunity to replace a skeletal structured control flow construct with a
+// single block.
+class StructuredConstructToBlockReductionOpportunity
+    : public ReductionOpportunity {
+ public:
+  // Constructs an opportunity from a header block id.
+  StructuredConstructToBlockReductionOpportunity(opt::IRContext* context,
+                                                 uint32_t construct_header)
+      : context_(context), construct_header_(construct_header) {}
+
+  // Returns true if and only if |construct_header_| exists in the module -
+  // another opportunity may have removed it.
+  bool PreconditionHolds() override;
+
+ protected:
+  void Apply() override;
+
+ private:
+  opt::IRContext* context_;
+  uint32_t construct_header_;
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..dc20f68
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.cpp
@@ -0,0 +1,185 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/structured_construct_to_block_reduction_opportunity_finder.h"
+
+#include <unordered_set>
+
+#include "source/reduce/structured_construct_to_block_reduction_opportunity.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<std::unique_ptr<ReductionOpportunity>>
+StructuredConstructToBlockReductionOpportunityFinder::GetAvailableOpportunities(
+    opt::IRContext* context, uint32_t target_function) const {
+  std::vector<std::unique_ptr<ReductionOpportunity>> result;
+
+  // Consider every function in the module.
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    // For every header block in the function, there is potentially a region of
+    // blocks that could be collapsed.
+    std::unordered_map<opt::BasicBlock*, std::unordered_set<opt::BasicBlock*>>
+        regions;
+
+    // Regions are identified using dominators and postdominators, so we compute
+    // those for the function.
+    auto* dominators = context->GetDominatorAnalysis(function);
+    auto* postdominators = context->GetPostDominatorAnalysis(function);
+
+    // Consider every block in the function.
+    for (auto& block : *function) {
+      // If a block has an unreachable predecessor then folding away a region in
+      // which that block is contained gets complicated, so we ignore regions
+      // that contain such blocks. We note whether this block suffers from this
+      // problem.
+      bool has_unreachable_predecessor =
+          HasUnreachablePredecessor(block, context);
+
+      // Look through all the regions we have identified so far to see whether
+      // this block is part of a region, or spoils a region (by having an
+      // unreachable predecessor).
+      for (auto entry = regions.begin(); entry != regions.end();) {
+        // |block| is in this region if it is dominated by the header,
+        // post-dominated by the merge, and different from the merge.
+        assert(&block != entry->first &&
+               "The block should not be the region's header because we only "
+               "make a region when we encounter its header.");
+        if (entry->first->MergeBlockId() != block.id() &&
+            dominators->Dominates(entry->first, &block) &&
+            postdominators->Dominates(
+                entry->first->GetMergeInst()->GetSingleWordInOperand(0),
+                block.id())) {
+          if (has_unreachable_predecessor) {
+            // The block would be in this region, but it has an unreachable
+            // predecessor. This spoils the region, so we remove it.
+            entry = regions.erase(entry);
+            continue;
+          } else {
+            // Add the block to the region.
+            entry->second.insert(&block);
+          }
+        }
+        ++entry;
+      }
+      if (block.MergeBlockIdIfAny() == 0) {
+        // The block isn't a header, so it doesn't constitute a new region.
+        continue;
+      }
+      if (!context->IsReachable(block)) {
+        // The block isn't reachable, so it doesn't constitute a new region.
+        continue;
+      }
+      auto* merge_block = context->cfg()->block(
+          block.GetMergeInst()->GetSingleWordInOperand(0));
+      if (!context->IsReachable(*merge_block)) {
+        // The block's merge is unreachable, so it doesn't constitute a new
+        // region.
+        continue;
+      }
+      assert(dominators->Dominates(&block, merge_block) &&
+             "The merge block is reachable, so the header must dominate it");
+      if (!postdominators->Dominates(merge_block, &block)) {
+        // The block is not post-dominated by its merge. This happens for
+        // instance when there is a break from a conditional, or an early exit.
+        // This also means that we don't add a region.
+        continue;
+      }
+      // We have a reachable header block with a rechable merge that
+      // postdominates the header: this means we have a new region.
+      regions.emplace(&block, std::unordered_set<opt::BasicBlock*>());
+    }
+
+    // Now that we have found all the regions and blocks within them, we check
+    // whether any region defines an id that is used outside the region. If this
+    // is *not* the case, then we have an opportunity to collapse the region
+    // down to its header block and merge block.
+    for (auto& entry : regions) {
+      if (DefinitionsRestrictedToRegion(*entry.first, entry.second, context)) {
+        result.emplace_back(
+            MakeUnique<StructuredConstructToBlockReductionOpportunity>(
+                context, entry.first->id()));
+      }
+    }
+  }
+  return result;
+}
+
+bool StructuredConstructToBlockReductionOpportunityFinder::
+    DefinitionsRestrictedToRegion(
+        const opt::BasicBlock& header,
+        const std::unordered_set<opt::BasicBlock*>& region,
+        opt::IRContext* context) {
+  // Consider every block in the region.
+  for (auto& block : region) {
+    // Consider every instruction in the block - this includes the label
+    // instruction
+    if (!block->WhileEachInst(
+            [context, &header, &region](opt::Instruction* inst) -> bool {
+              if (inst->result_id() == 0) {
+                // The instruction does not genreate a result id, thus it cannot
+                // be referred to outside the region - this is fine.
+                return true;
+              }
+              // Consider every use of the instruction's result id.
+              if (!context->get_def_use_mgr()->WhileEachUse(
+                      inst->result_id(),
+                      [context, &header, &region](opt::Instruction* user,
+                                                  uint32_t) -> bool {
+                        auto user_block = context->get_instr_block(user);
+                        if (user == header.GetMergeInst() ||
+                            user == header.terminator()) {
+                          // We are going to delete the header's merge
+                          // instruction and rewrite its terminator, so it does
+                          // not matter if the user is one of these
+                          // instructions.
+                          return true;
+                        }
+                        if (user_block == nullptr ||
+                            region.count(user_block) == 0) {
+                          // The user is either a global instruction, or an
+                          // instruction in a block outside the region. Removing
+                          // the region would invalidate this user.
+                          return false;
+                        }
+                        return true;
+                      })) {
+                return false;
+              }
+              return true;
+            })) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool StructuredConstructToBlockReductionOpportunityFinder::
+    HasUnreachablePredecessor(const opt::BasicBlock& block,
+                              opt::IRContext* context) {
+  for (auto pred : context->cfg()->preds(block.id())) {
+    if (!context->IsReachable(*context->cfg()->block(pred))) {
+      return true;
+    }
+  }
+  return false;
+}
+
+std::string StructuredConstructToBlockReductionOpportunityFinder::GetName()
+    const {
+  return "StructuredConstructToBlockReductionOpportunityFinder";
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/structured_construct_to_block_reduction_opportunity_finder.h b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.h
new file mode 100644
index 0000000..28bbc17
--- /dev/null
+++ b/source/reduce/structured_construct_to_block_reduction_opportunity_finder.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H
+#define SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+// A finder for opportunities to replace a skeletal structured control flow
+// construct - that is, a construct that does not define anything that's used
+// outside the construct - into its header block.
+class StructuredConstructToBlockReductionOpportunityFinder
+    : public ReductionOpportunityFinder {
+ public:
+  StructuredConstructToBlockReductionOpportunityFinder() = default;
+
+  ~StructuredConstructToBlockReductionOpportunityFinder() override = default;
+
+  std::string GetName() const final;
+
+  std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
+      opt::IRContext* context, uint32_t target_function) const final;
+
+ private:
+  // Returns true if and only if all instructions defined in |region| are used
+  // only inside |region|, with the exception that they may be used by the merge
+  // or terminator instruction of |header|, which must be the header block for
+  // the region.
+  static bool DefinitionsRestrictedToRegion(
+      const opt::BasicBlock& header,
+      const std::unordered_set<opt::BasicBlock*>& region,
+      opt::IRContext* context);
+
+  // Returns true if and only if |block| has at least one predecessor that is
+  // unreachable in the control flow graph of its function.
+  static bool HasUnreachablePredecessor(const opt::BasicBlock& block,
+                                        opt::IRContext* context);
+};
+
+}  // namespace reduce
+}  // namespace spvtools
+
+#endif  // SOURCE_REDUCE_STRUCTURED_CONSTRUCT_TO_BLOCK_REDUCTION_OPPORTUNITY_FINDER_H
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index 0c00443..850af45 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -27,16 +27,14 @@
 
 bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() {
   // Is the loop header reachable?
-  return loop_construct_header_->GetLabel()
-      ->context()
-      ->GetDominatorAnalysis(enclosing_function_)
-      ->IsReachable(loop_construct_header_);
+  return loop_construct_header_->GetLabel()->context()->IsReachable(
+      *loop_construct_header_);
 }
 
 void StructuredLoopToSelectionReductionOpportunity::Apply() {
   // Force computation of dominator analysis, CFG and structured CFG analysis
   // before we start to mess with edges in the function.
-  context_->GetDominatorAnalysis(enclosing_function_);
+  context_->GetDominatorAnalysis(loop_construct_header_->GetParent());
   context_->cfg();
   context_->GetStructuredCFGAnalysis();
 
@@ -78,8 +76,7 @@
     }
     already_seen.insert(pred);
 
-    if (!context_->GetDominatorAnalysis(enclosing_function_)
-             ->IsReachable(pred)) {
+    if (!context_->IsReachable(*context_->cfg()->block(pred))) {
       // We do not care about unreachable predecessors (and dominance
       // information, and thus the notion of structured control flow, makes
       // little sense for unreachable blocks).
@@ -216,7 +213,7 @@
 
 void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() {
   // Consider each instruction in the function.
-  for (auto& block : *enclosing_function_) {
+  for (auto& block : *loop_construct_header_->GetParent()) {
     for (auto& def : block) {
       if (def.opcode() == SpvOpVariable) {
         // Variables are defined at the start of the function, and can be
@@ -243,7 +240,7 @@
               case SpvStorageClassFunction:
                 use->SetOperand(
                     index, {FindOrCreateFunctionVariable(
-                               context_, enclosing_function_,
+                               context_, loop_construct_header_->GetParent(),
                                context_->get_type_mgr()->GetId(pointer_type))});
                 break;
               default:
@@ -276,11 +273,11 @@
   if (use->opcode() == SpvOpPhi) {
     // A use in a phi doesn't need to be dominated by its definition, but the
     // associated parent block does need to be dominated by the definition.
-    return context_->GetDominatorAnalysis(enclosing_function_)
+    return context_->GetDominatorAnalysis(loop_construct_header_->GetParent())
         ->Dominates(def_block.id(), use->GetSingleWordOperand(use_index + 1));
   }
   // In non-phi cases, a use needs to be dominated by its definition.
-  return context_->GetDominatorAnalysis(enclosing_function_)
+  return context_->GetDominatorAnalysis(loop_construct_header_->GetParent())
       ->Dominates(def, use);
 }
 
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
index 4c57619..0e3c840 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
-#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
+#ifndef SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_H_
+#define SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_H_
 
 #include "source/opt/def_use_manager.h"
 #include "source/opt/dominator_analysis.h"
@@ -30,11 +30,8 @@
   // Constructs an opportunity from a loop header block and the function that
   // encloses it.
   explicit StructuredLoopToSelectionReductionOpportunity(
-      opt::IRContext* context, opt::BasicBlock* loop_construct_header,
-      opt::Function* enclosing_function)
-      : context_(context),
-        loop_construct_header_(loop_construct_header),
-        enclosing_function_(enclosing_function) {}
+      opt::IRContext* context, opt::BasicBlock* loop_construct_header)
+      : context_(context), loop_construct_header_(loop_construct_header) {}
 
   // Returns true if the loop header is reachable.  A structured loop might
   // become unreachable as a result of turning another structured loop into
@@ -88,10 +85,9 @@
 
   opt::IRContext* context_;
   opt::BasicBlock* loop_construct_header_;
-  opt::Function* enclosing_function_;
 };
 
 }  // namespace reduce
 }  // namespace spvtools
 
-#endif  // SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_
+#endif  // SOURCE_REDUCE_STRUCTURED_LOOP_TO_SELECTION_REDUCTION_OPPORTUNITY_H_
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
index fdf3ab0..3fe6128 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
@@ -86,8 +86,8 @@
       // We can turn this structured loop into a selection, so add the
       // opportunity to do so.
       result.push_back(
-          MakeUnique<StructuredLoopToSelectionReductionOpportunity>(
-              context, &block, function));
+          MakeUnique<StructuredLoopToSelectionReductionOpportunity>(context,
+                                                                    &block));
     }
   }
   return result;
diff --git a/source/spirv_constant.h b/source/spirv_constant.h
index 39771cc..8636806 100644
--- a/source/spirv_constant.h
+++ b/source/spirv_constant.h
@@ -84,6 +84,7 @@
   SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR = 6,
   SPV_GENERATOR_KHRONOS_ASSEMBLER = 7,
   SPV_GENERATOR_KHRONOS_GLSLANG = 8,
+  SPV_GENERATOR_KHRONOS_LINKER = 17,
   SPV_GENERATOR_NUM_ENTRIES,
   SPV_FORCE_16_BIT_ENUM(spv_generator_t)
 } spv_generator_t;
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index f20ebb4..187ab61 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -62,7 +62,7 @@
     case SPV_ENV_VULKAN_1_1:
       return "SPIR-V 1.3 (under Vulkan 1.1 semantics)";
     case SPV_ENV_WEBGPU_0:
-      assert(false);
+      assert(false && "Deprecated target environment value.");
       break;
     case SPV_ENV_UNIVERSAL_1_4:
       return "SPIR-V 1.4";
@@ -72,6 +72,9 @@
       return "SPIR-V 1.5";
     case SPV_ENV_VULKAN_1_2:
       return "SPIR-V 1.5 (under Vulkan 1.2 semantics)";
+    case SPV_ENV_MAX:
+      assert(false && "Invalid target environment value.");
+      break;
   }
   return "";
 }
@@ -102,7 +105,7 @@
     case SPV_ENV_VULKAN_1_1:
       return SPV_SPIRV_VERSION_WORD(1, 3);
     case SPV_ENV_WEBGPU_0:
-      assert(false);
+      assert(false && "Deprecated target environment value.");
       break;
     case SPV_ENV_UNIVERSAL_1_4:
     case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
@@ -110,6 +113,9 @@
     case SPV_ENV_UNIVERSAL_1_5:
     case SPV_ENV_VULKAN_1_2:
       return SPV_SPIRV_VERSION_WORD(1, 5);
+    case SPV_ENV_MAX:
+      assert(false && "Invalid target environment value.");
+      break;
   }
   return SPV_SPIRV_VERSION_WORD(0, 0);
 }
@@ -212,7 +218,10 @@
     case SPV_ENV_VULKAN_1_2:
       return true;
     case SPV_ENV_WEBGPU_0:
-      assert(false);
+      assert(false && "Deprecated target environment value.");
+      break;
+    case SPV_ENV_MAX:
+      assert(false && "Invalid target environment value.");
       break;
   }
   return false;
@@ -246,7 +255,10 @@
     case SPV_ENV_OPENCL_2_2:
       return true;
     case SPV_ENV_WEBGPU_0:
-      assert(false);
+      assert(false && "Deprecated target environment value.");
+      break;
+    case SPV_ENV_MAX:
+      assert(false && "Invalid target environment value.");
       break;
   }
   return false;
@@ -280,7 +292,43 @@
     case SPV_ENV_OPENGL_4_5:
       return true;
     case SPV_ENV_WEBGPU_0:
-      assert(false);
+      assert(false && "Deprecated target environment value.");
+      break;
+    case SPV_ENV_MAX:
+      assert(false && "Invalid target environment value.");
+      break;
+  }
+  return false;
+}
+
+bool spvIsValidEnv(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_UNIVERSAL_1_4:
+    case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+    case SPV_ENV_UNIVERSAL_1_5:
+    case SPV_ENV_VULKAN_1_2:
+    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;
+    case SPV_ENV_WEBGPU_0:
+    case SPV_ENV_MAX:
       break;
   }
   return false;
@@ -320,7 +368,10 @@
       return "Universal";
     }
     case SPV_ENV_WEBGPU_0:
-      assert(false);
+      assert(false && "Deprecated target environment value.");
+      break;
+    case SPV_ENV_MAX:
+      assert(false && "Invalid target environment value.");
       break;
   }
   return "Unknown";
diff --git a/source/spirv_target_env.h b/source/spirv_target_env.h
index a804d61..cc06dec 100644
--- a/source/spirv_target_env.h
+++ b/source/spirv_target_env.h
@@ -28,6 +28,9 @@
 // Returns true if |env| is an OPENGL environment, false otherwise.
 bool spvIsOpenGLEnv(spv_target_env env);
 
+// Returns true if |env| is an implemented/valid environment, false otherwise.
+bool spvIsValidEnv(spv_target_env env);
+
 // Returns the version number for the given SPIR-V target environment.
 uint32_t spvVersionForTargetEnv(spv_target_env env);
 
diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp
index 01aa797..e5b1eec 100644
--- a/source/spirv_validator_options.cpp
+++ b/source/spirv_validator_options.cpp
@@ -111,7 +111,17 @@
   options->scalar_block_layout = val;
 }
 
+void spvValidatorOptionsSetWorkgroupScalarBlockLayout(spv_validator_options options,
+                                                      bool val) {
+  options->workgroup_scalar_block_layout = val;
+}
+
 void spvValidatorOptionsSetSkipBlockLayout(spv_validator_options options,
                                            bool val) {
   options->skip_block_layout = val;
 }
+
+void spvValidatorOptionsSetAllowLocalSizeId(spv_validator_options options,
+                                            bool val) {
+  options->allow_localsizeid = val;
+}
diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h
index b7da5d8..a357c03 100644
--- a/source/spirv_validator_options.h
+++ b/source/spirv_validator_options.h
@@ -45,7 +45,9 @@
         relax_block_layout(false),
         uniform_buffer_standard_layout(false),
         scalar_block_layout(false),
+        workgroup_scalar_block_layout(false),
         skip_block_layout(false),
+        allow_localsizeid(false),
         before_hlsl_legalization(false) {}
 
   validator_universal_limits_t universal_limits_;
@@ -54,7 +56,9 @@
   bool relax_block_layout;
   bool uniform_buffer_standard_layout;
   bool scalar_block_layout;
+  bool workgroup_scalar_block_layout;
   bool skip_block_layout;
+  bool allow_localsizeid;
   bool before_hlsl_legalization;
 };
 
diff --git a/source/text_handler.cpp b/source/text_handler.cpp
index c31f34a..46b9845 100644
--- a/source/text_handler.cpp
+++ b/source/text_handler.cpp
@@ -120,7 +120,8 @@
         case '\n':
         case '\r':
           if (escaping || quoting) break;
-        // Fall through.
+          word->assign(text->str + start_index, text->str + position->index);
+          return SPV_SUCCESS;
         case '\0': {  // NOTE: End of word found!
           word->assign(text->str + start_index, text->str + position->index);
           return SPV_SUCCESS;
diff --git a/source/util/hex_float.h b/source/util/hex_float.h
index cfc40fa..be28eae 100644
--- a/source/util/hex_float.h
+++ b/source/util/hex_float.h
@@ -1005,6 +1005,9 @@
     is.get();
     next_char = is.peek();
   }
+
+  // Finished reading the part preceding any '.' or 'p'.
+
   bits_written = false;
   while (seen_dot && !seen_p) {
     // Handle only fractional parts now.
@@ -1037,11 +1040,16 @@
     next_char = is.peek();
   }
 
+  // Finished reading the part preceding 'p'.
+  // In hex floats syntax, the binary exponent is required.
+
   bool seen_sign = false;
   int8_t exponent_sign = 1;
+  bool seen_written_exponent_digits = false;
   int_type written_exponent = 0;
   while (true) {
-    if ((next_char == '-' || next_char == '+')) {
+    if (!seen_written_exponent_digits &&
+        (next_char == '-' || next_char == '+')) {
       if (seen_sign) {
         is.setstate(std::ios::failbit);
         return is;
@@ -1049,6 +1057,7 @@
       seen_sign = true;
       exponent_sign = (next_char == '-') ? -1 : 1;
     } else if (::isdigit(next_char)) {
+      seen_written_exponent_digits = true;
       // Hex-floats express their exponent as decimal.
       written_exponent = static_cast<int_type>(written_exponent * 10);
       written_exponent =
@@ -1059,6 +1068,11 @@
     is.get();
     next_char = is.peek();
   }
+  if (!seen_written_exponent_digits) {
+    // Binary exponent had no digits.
+    is.setstate(std::ios::failbit);
+    return is;
+  }
 
   written_exponent = static_cast<int_type>(written_exponent * exponent_sign);
   exponent = static_cast<int_type>(exponent + written_exponent);
diff --git a/source/val/basic_block.h b/source/val/basic_block.h
index 5eea4f9..5af4b9e 100644
--- a/source/val/basic_block.h
+++ b/source/val/basic_block.h
@@ -139,9 +139,14 @@
   /// @brief A BasicBlock dominator iterator class
   ///
   /// This iterator will iterate over the (post)dominators of the block
-  class DominatorIterator
-      : public std::iterator<std::forward_iterator_tag, BasicBlock*> {
+  class DominatorIterator {
    public:
+    using iterator_category = std::forward_iterator_tag;
+    using value_type = BasicBlock*;
+    using pointer = value_type*;
+    using reference = value_type&;
+    using difference_type = std::ptrdiff_t;
+
     /// @brief Constructs the end of dominator iterator
     ///
     /// This will create an iterator which will represent the element
diff --git a/source/val/construct.cpp b/source/val/construct.cpp
index 733856c..251e2bb 100644
--- a/source/val/construct.cpp
+++ b/source/val/construct.cpp
@@ -78,7 +78,12 @@
   ConstructBlockSet construct_blocks;
   std::unordered_set<BasicBlock*> corresponding_headers;
   for (auto& other : corresponding_constructs()) {
-    corresponding_headers.insert(other->entry_block());
+    // The corresponding header can be the same block as this construct's
+    // header for loops with no loop construct. In those cases, don't add the
+    // loop header as it prevents finding any blocks in the construct.
+    if (type() != ConstructType::kContinue || other->entry_block() != header) {
+      corresponding_headers.insert(other->entry_block());
+    }
   }
   std::vector<BasicBlock*> stack;
   stack.push_back(const_cast<BasicBlock*>(header));
@@ -176,8 +181,9 @@
       for (auto& use : block->label()->uses()) {
         if ((use.first->opcode() == SpvOpLoopMerge ||
              use.first->opcode() == SpvOpSelectionMerge) &&
-            use.second == 1)
+            use.second == 1 && use.first->block()->dominates(*block)) {
           return use.first->block();
+        }
       }
       return block->immediate_dominator();
     };
diff --git a/source/val/function.cpp b/source/val/function.cpp
index 249c866..9ad68e8 100644
--- a/source/val/function.cpp
+++ b/source/val/function.cpp
@@ -308,6 +308,9 @@
   if (block_depth_.find(bb) != block_depth_.end()) {
     return block_depth_[bb];
   }
+  // Avoid recursion. Something is wrong if the same block is encountered
+  // multiple times.
+  block_depth_[bb] = 0;
 
   BasicBlock* bb_dom = bb->immediate_dominator();
   if (!bb_dom || bb == bb_dom) {
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index a2e116b..45b6a46 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -143,6 +143,7 @@
       if (_.recursive_entry_points().find(entry_point) !=
           _.recursive_entry_points().end()) {
         return _.diag(SPV_ERROR_INVALID_BINARY, _.FindDef(entry_point))
+               << _.VkErrorID(4634)
                << "Entry points may not have a call graph with cycles.";
       }
     }
diff --git a/source/val/validate_adjacency.cpp b/source/val/validate_adjacency.cpp
index 64655b0..8e6c373 100644
--- a/source/val/validate_adjacency.cpp
+++ b/source/val/validate_adjacency.cpp
@@ -60,7 +60,10 @@
         // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We need
         // to discuss the location of DebugScope, DebugNoScope, DebugDeclare,
         // and DebugValue.
-        if (!spvExtInstIsDebugInfo(inst.ext_inst_type())) {
+        // NOTE: This does not apply to the non-semantic vulkan debug info.
+        if (!spvExtInstIsDebugInfo(inst.ext_inst_type()) ||
+            inst.ext_inst_type() ==
+                SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
           adjacency_status = PHI_AND_VAR_INVALID;
         }
         break;
diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp
index 85d2b75..16d4490 100644
--- a/source/val/validate_annotation.cpp
+++ b/source/val/validate_annotation.cpp
@@ -138,14 +138,14 @@
       return "PerTaskNV";
     case SpvDecorationPerVertexNV:
       return "PerVertexNV";
-    case SpvDecorationNonUniformEXT:
-      return "NonUniformEXT";
-    case SpvDecorationRestrictPointerEXT:
-      return "RestrictPointerEXT";
-    case SpvDecorationAliasedPointerEXT:
-      return "AliasedPointerEXT";
-    case SpvDecorationHlslCounterBufferGOOGLE:
-      return "HlslCounterBufferGOOGLE";
+    case SpvDecorationNonUniform:
+      return "NonUniform";
+    case SpvDecorationRestrictPointer:
+      return "RestrictPointer";
+    case SpvDecorationAliasedPointer:
+      return "AliasedPointer";
+    case SpvDecorationCounterBuffer:
+      return "CounterBuffer";
     case SpvDecorationHlslSemanticGOOGLE:
       return "HlslSemanticGOOGLE";
     default:
@@ -156,8 +156,8 @@
 
 // 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)) {
+bool DecorationTakesIdParameters(SpvDecoration type) {
+  switch (type) {
     case SpvDecorationUniformId:
     case SpvDecorationAlignmentId:
     case SpvDecorationMaxByteOffsetId:
@@ -169,17 +169,212 @@
   return false;
 }
 
-spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) {
-  const auto decoration = inst->GetOperandAs<uint32_t>(1);
-  if (decoration == SpvDecorationSpecId) {
-    const auto target_id = inst->GetOperandAs<uint32_t>(0);
-    const auto target = _.FindDef(target_id);
-    if (!target || !spvOpcodeIsScalarSpecConstant(target->opcode())) {
-      return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << "OpDecorate SpecId decoration target <id> '"
-             << _.getIdName(target_id)
-             << "' is not a scalar specialization constant.";
+bool IsMemberDecorationOnly(SpvDecoration dec) {
+  switch (dec) {
+    case SpvDecorationRowMajor:
+    case SpvDecorationColMajor:
+    case SpvDecorationMatrixStride:
+      // SPIR-V spec bug? Offset is generated on variables when dealing with
+      // transform feedback.
+      // case SpvDecorationOffset:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+bool IsNotMemberDecoration(SpvDecoration dec) {
+  switch (dec) {
+    case SpvDecorationSpecId:
+    case SpvDecorationBlock:
+    case SpvDecorationBufferBlock:
+    case SpvDecorationArrayStride:
+    case SpvDecorationGLSLShared:
+    case SpvDecorationGLSLPacked:
+    case SpvDecorationCPacked:
+    // TODO: https://github.com/KhronosGroup/glslang/issues/703:
+    // glslang applies Restrict to structure members.
+    // case SpvDecorationRestrict:
+    case SpvDecorationAliased:
+    case SpvDecorationConstant:
+    case SpvDecorationUniform:
+    case SpvDecorationUniformId:
+    case SpvDecorationSaturatedConversion:
+    case SpvDecorationIndex:
+    case SpvDecorationBinding:
+    case SpvDecorationDescriptorSet:
+    case SpvDecorationFuncParamAttr:
+    case SpvDecorationFPRoundingMode:
+    case SpvDecorationFPFastMathMode:
+    case SpvDecorationLinkageAttributes:
+    case SpvDecorationNoContraction:
+    case SpvDecorationInputAttachmentIndex:
+    case SpvDecorationAlignment:
+    case SpvDecorationMaxByteOffset:
+    case SpvDecorationAlignmentId:
+    case SpvDecorationMaxByteOffsetId:
+    case SpvDecorationNoSignedWrap:
+    case SpvDecorationNoUnsignedWrap:
+    case SpvDecorationNonUniform:
+    case SpvDecorationRestrictPointer:
+    case SpvDecorationAliasedPointer:
+    case SpvDecorationCounterBuffer:
+      return true;
+    default:
+      break;
+  }
+  return false;
+}
+
+spv_result_t ValidateDecorationTarget(ValidationState_t& _, SpvDecoration dec,
+                                      const Instruction* inst,
+                                      const Instruction* target) {
+  auto fail = [&_, dec, inst, target](uint32_t vuid = 0) -> DiagnosticStream {
+    DiagnosticStream ds = std::move(
+        _.diag(SPV_ERROR_INVALID_ID, inst)
+        << _.VkErrorID(vuid) << LogStringForDecoration(dec)
+        << " decoration on target <id> '" << _.getIdName(target->id()) << "' ");
+    return ds;
+  };
+  switch (dec) {
+    case SpvDecorationSpecId:
+      if (!spvOpcodeIsScalarSpecConstant(target->opcode())) {
+        return fail() << "must be a scalar specialization constant";
+      }
+      break;
+    case SpvDecorationBlock:
+    case SpvDecorationBufferBlock:
+    case SpvDecorationGLSLShared:
+    case SpvDecorationGLSLPacked:
+    case SpvDecorationCPacked:
+      if (target->opcode() != SpvOpTypeStruct) {
+        return fail() << "must be a structure type";
+      }
+      break;
+    case SpvDecorationArrayStride:
+      if (target->opcode() != SpvOpTypeArray &&
+          target->opcode() != SpvOpTypeRuntimeArray &&
+          target->opcode() != SpvOpTypePointer) {
+        return fail() << "must be an array or pointer type";
+      }
+      break;
+    case SpvDecorationBuiltIn:
+      if (target->opcode() != SpvOpVariable &&
+          !spvOpcodeIsConstant(target->opcode())) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "BuiltIns can only target variables, structure members or "
+                  "constants";
+      }
+      if (inst->GetOperandAs<SpvBuiltIn>(2) == SpvBuiltInWorkgroupSize) {
+        if (!spvOpcodeIsConstant(target->opcode())) {
+          return fail() << "must be a constant for WorkgroupSize";
+        }
+      } else if (target->opcode() != SpvOpVariable) {
+        return fail() << "must be a variable";
+      }
+      break;
+    case SpvDecorationNoPerspective:
+    case SpvDecorationFlat:
+    case SpvDecorationPatch:
+    case SpvDecorationCentroid:
+    case SpvDecorationSample:
+    case SpvDecorationRestrict:
+    case SpvDecorationAliased:
+    case SpvDecorationVolatile:
+    case SpvDecorationCoherent:
+    case SpvDecorationNonWritable:
+    case SpvDecorationNonReadable:
+    case SpvDecorationXfbBuffer:
+    case SpvDecorationXfbStride:
+    case SpvDecorationComponent:
+    case SpvDecorationStream:
+    case SpvDecorationRestrictPointer:
+    case SpvDecorationAliasedPointer:
+      if (target->opcode() != SpvOpVariable &&
+          target->opcode() != SpvOpFunctionParameter) {
+        return fail() << "must be a memory object declaration";
+      }
+      if (_.GetIdOpcode(target->type_id()) != SpvOpTypePointer) {
+        return fail() << "must be a pointer type";
+      }
+      break;
+    case SpvDecorationInvariant:
+    case SpvDecorationConstant:
+    case SpvDecorationLocation:
+    case SpvDecorationIndex:
+    case SpvDecorationBinding:
+    case SpvDecorationDescriptorSet:
+    case SpvDecorationInputAttachmentIndex:
+      if (target->opcode() != SpvOpVariable) {
+        return fail() << "must be a variable";
+      }
+      break;
+    default:
+      break;
+  }
+
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    // The following were all checked as pointer types above.
+    SpvStorageClass sc = SpvStorageClassUniform;
+    const auto type = _.FindDef(target->type_id());
+    if (type && type->operands().size() > 2) {
+      sc = type->GetOperandAs<SpvStorageClass>(1);
     }
+    switch (dec) {
+      case SpvDecorationLocation:
+      case SpvDecorationComponent:
+        // Location is used for input, output and ray tracing stages.
+        if (sc == SpvStorageClassStorageBuffer ||
+            sc == SpvStorageClassUniform ||
+            sc == SpvStorageClassUniformConstant ||
+            sc == SpvStorageClassWorkgroup || sc == SpvStorageClassPrivate ||
+            sc == SpvStorageClassFunction) {
+          return _.diag(SPV_ERROR_INVALID_ID, target)
+                 << LogStringForDecoration(dec)
+                 << " decoration must not be applied to this storage class";
+        }
+        break;
+      case SpvDecorationIndex:
+        if (sc != SpvStorageClassOutput) {
+          return fail() << "must be in the Output storage class";
+        }
+        break;
+      case SpvDecorationBinding:
+      case SpvDecorationDescriptorSet:
+        if (sc != SpvStorageClassStorageBuffer &&
+            sc != SpvStorageClassUniform &&
+            sc != SpvStorageClassUniformConstant) {
+          return fail() << "must be in the StorageBuffer, Uniform, or "
+                           "UniformConstant storage class";
+        }
+        break;
+      case SpvDecorationInputAttachmentIndex:
+        if (sc != SpvStorageClassUniformConstant) {
+          return fail() << "must be in the UniformConstant storage class";
+        }
+        break;
+      case SpvDecorationFlat:
+      case SpvDecorationNoPerspective:
+      case SpvDecorationCentroid:
+      case SpvDecorationSample:
+        if (sc != SpvStorageClassInput && sc != SpvStorageClassOutput) {
+          return fail(4670) << "storage class must be Input or Output";
+        }
+        break;
+      default:
+        break;
+    }
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) {
+  const auto decoration = inst->GetOperandAs<SpvDecoration>(1);
+  const auto target_id = inst->GetOperandAs<uint32_t>(0);
+  const auto target = _.FindDef(target_id);
+  if (!target) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst) << "target is not defined";
   }
 
   if (spvIsVulkanEnv(_.context()->target_env)) {
@@ -197,17 +392,34 @@
            << "Decorations taking ID parameters may not be used with "
               "OpDecorateId";
   }
+
+  if (target->opcode() != SpvOpDecorationGroup) {
+    if (IsMemberDecorationOnly(decoration)) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << LogStringForDecoration(decoration)
+             << " can only be applied to structure members";
+    }
+
+    if (auto error = ValidateDecorationTarget(_, decoration, inst, target)) {
+      return error;
+    }
+  }
+
   // 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);
+  const auto decoration = inst->GetOperandAs<SpvDecoration>(1);
   if (!DecorationTakesIdParameters(decoration)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
            << "Decorations that don't take ID parameters may not be used with "
               "OpDecorateId";
   }
+
+  // No member decorations take id parameters, so we don't bother checking if
+  // we are using a member only decoration here.
+
   // TODO: Add validations for these decorations.
   // UniformId is covered elsewhere.
   return SPV_SUCCESS;
@@ -234,6 +446,13 @@
            << " members. Largest valid index is " << member_count - 1 << ".";
   }
 
+  const auto decoration = inst->GetOperandAs<SpvDecoration>(2);
+  if (IsNotMemberDecoration(decoration)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << LogStringForDecoration(decoration)
+           << " cannot be applied to structure members";
+  }
+
   return SPV_SUCCESS;
 }
 
diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp
index dd263a7..cfa15d9 100644
--- a/source/val/validate_atomics.cpp
+++ b/source/val/validate_atomics.cpp
@@ -47,25 +47,31 @@
   }
 }
 
-}  // namespace
-
-namespace spvtools {
-namespace val {
-
-// Validates correctness of atomic instructions.
-spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
-  const SpvOp opcode = inst->opcode();
-  const uint32_t result_type = inst->type_id();
-  bool is_atomic_float_opcode = false;
-  if (opcode == SpvOpAtomicLoad || opcode == SpvOpAtomicStore ||
-      opcode == SpvOpAtomicFAddEXT || opcode == SpvOpAtomicExchange) {
-    is_atomic_float_opcode = true;
-  }
+bool HasReturnType(uint32_t opcode) {
   switch (opcode) {
-    case SpvOpAtomicLoad:
     case SpvOpAtomicStore:
-    case SpvOpAtomicExchange:
+    case SpvOpAtomicFlagClear:
+      return false;
+      break;
+    default:
+      return true;
+  }
+}
+
+bool HasOnlyFloatReturnType(uint32_t opcode) {
+  switch (opcode) {
     case SpvOpAtomicFAddEXT:
+    case SpvOpAtomicFMinEXT:
+    case SpvOpAtomicFMaxEXT:
+      return true;
+      break;
+    default:
+      return false;
+  }
+}
+
+bool HasOnlyIntReturnType(uint32_t opcode) {
+  switch (opcode) {
     case SpvOpAtomicCompareExchange:
     case SpvOpAtomicCompareExchangeWeak:
     case SpvOpAtomicIIncrement:
@@ -79,123 +85,96 @@
     case SpvOpAtomicAnd:
     case SpvOpAtomicOr:
     case SpvOpAtomicXor:
+      return true;
+      break;
+    default:
+      return false;
+  }
+}
+
+bool HasIntOrFloatReturnType(uint32_t opcode) {
+  switch (opcode) {
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicExchange:
+      return true;
+      break;
+    default:
+      return false;
+  }
+}
+
+bool HasOnlyBoolReturnType(uint32_t opcode) {
+  switch (opcode) {
+    case SpvOpAtomicFlagTestAndSet:
+      return true;
+      break;
+    default:
+      return false;
+  }
+}
+
+}  // namespace
+
+namespace spvtools {
+namespace val {
+
+// Validates correctness of atomic instructions.
+spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) {
+  const SpvOp opcode = inst->opcode();
+  switch (opcode) {
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicStore:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicFAddEXT:
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicFMinEXT:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicFMaxEXT:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
     case SpvOpAtomicFlagTestAndSet:
     case SpvOpAtomicFlagClear: {
-      if (_.HasCapability(SpvCapabilityKernel) &&
-          (opcode == SpvOpAtomicLoad || opcode == SpvOpAtomicExchange ||
-           opcode == SpvOpAtomicCompareExchange)) {
-        if (!_.IsFloatScalarType(result_type) &&
-            !_.IsIntScalarType(result_type)) {
+      const uint32_t result_type = inst->type_id();
+
+      // All current atomics only are scalar result
+      // Validate return type first so can just check if pointer type is same
+      // (if applicable)
+      if (HasReturnType(opcode)) {
+        if (HasOnlyFloatReturnType(opcode) &&
+            !_.IsFloatScalarType(result_type)) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << spvOpcodeString(opcode)
-                 << ": expected Result Type to be int or float scalar type";
-        }
-      } else if (opcode == SpvOpAtomicFlagTestAndSet) {
-        if (!_.IsBoolScalarType(result_type)) {
+                 << ": expected Result Type to be float scalar type";
+        } else if (HasOnlyIntReturnType(opcode) &&
+                   !_.IsIntScalarType(result_type)) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << spvOpcodeString(opcode)
+                 << ": expected Result Type to be integer scalar type";
+        } else if (HasIntOrFloatReturnType(opcode) &&
+                   !_.IsFloatScalarType(result_type) &&
+                   !_.IsIntScalarType(result_type)) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << spvOpcodeString(opcode)
+                 << ": expected Result Type to be integer or float scalar type";
+        } else if (HasOnlyBoolReturnType(opcode) &&
+                   !_.IsBoolScalarType(result_type)) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << spvOpcodeString(opcode)
                  << ": expected Result Type to be bool scalar type";
         }
-      } else if (opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore) {
-        assert(result_type == 0);
-      } else {
-        if (_.IsFloatScalarType(result_type)) {
-          if (is_atomic_float_opcode) {
-            if (opcode == SpvOpAtomicFAddEXT) {
-              if ((_.GetBitWidth(result_type) == 32) &&
-                  (!_.HasCapability(SpvCapabilityAtomicFloat32AddEXT))) {
-                return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                       << spvOpcodeString(opcode)
-                       << ": float add atomics require the AtomicFloat32AddEXT "
-                          "capability";
-              }
-              if ((_.GetBitWidth(result_type) == 64) &&
-                  (!_.HasCapability(SpvCapabilityAtomicFloat64AddEXT))) {
-                return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                       << spvOpcodeString(opcode)
-                       << ": float add atomics require the AtomicFloat64AddEXT "
-                          "capability";
-              }
-            }
-          } else {
-            return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                   << spvOpcodeString(opcode)
-                   << ": expected Result Type to be int scalar type";
-          }
-        } else if (_.IsIntScalarType(result_type) &&
-                   opcode == SpvOpAtomicFAddEXT) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << spvOpcodeString(opcode)
-                 << ": expected Result Type to be float scalar type";
-        } else if (!_.IsFloatScalarType(result_type) &&
-                   !_.IsIntScalarType(result_type)) {
-          switch (opcode) {
-            case SpvOpAtomicFAddEXT:
-              return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                     << spvOpcodeString(opcode)
-                     << ": expected Result Type to be float scalar type";
-            case SpvOpAtomicIIncrement:
-            case SpvOpAtomicIDecrement:
-            case SpvOpAtomicIAdd:
-            case SpvOpAtomicISub:
-            case SpvOpAtomicSMin:
-            case SpvOpAtomicSMax:
-            case SpvOpAtomicUMin:
-            case SpvOpAtomicUMax:
-              return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                     << spvOpcodeString(opcode)
-                     << ": expected Result Type to be integer scalar type";
-            default:
-              return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                     << spvOpcodeString(opcode)
-                     << ": expected Result Type to be int or float scalar type";
-          }
-        }
-
-        if (spvIsVulkanEnv(_.context()->target_env) &&
-            (_.GetBitWidth(result_type) != 32 &&
-             (_.GetBitWidth(result_type) != 64 ||
-              !_.HasCapability(SpvCapabilityInt64ImageEXT)))) {
-          switch (opcode) {
-            case SpvOpAtomicSMin:
-            case SpvOpAtomicUMin:
-            case SpvOpAtomicSMax:
-            case SpvOpAtomicUMax:
-            case SpvOpAtomicAnd:
-            case SpvOpAtomicOr:
-            case SpvOpAtomicXor:
-            case SpvOpAtomicIAdd:
-            case SpvOpAtomicISub:
-            case SpvOpAtomicFAddEXT:
-            case SpvOpAtomicLoad:
-            case SpvOpAtomicStore:
-            case SpvOpAtomicExchange:
-            case SpvOpAtomicIIncrement:
-            case SpvOpAtomicIDecrement:
-            case SpvOpAtomicCompareExchangeWeak:
-            case SpvOpAtomicCompareExchange: {
-              if (_.GetBitWidth(result_type) == 64 &&
-                  _.IsIntScalarType(result_type) &&
-                  !_.HasCapability(SpvCapabilityInt64Atomics))
-                return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                       << spvOpcodeString(opcode)
-                       << ": 64-bit atomics require the Int64Atomics "
-                          "capability";
-            } break;
-            default:
-              return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                     << spvOpcodeString(opcode)
-                     << ": according to the Vulkan spec atomic Result Type "
-                        "needs "
-                        "to be a 32-bit int scalar type";
-          }
-        }
       }
 
-      uint32_t operand_index =
-          opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore ? 0 : 2;
+      uint32_t operand_index = HasReturnType(opcode) ? 2 : 0;
       const uint32_t pointer_type = _.GetOperandTypeId(inst, operand_index++);
-
       uint32_t data_type = 0;
       uint32_t storage_class = 0;
       if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) {
@@ -204,6 +183,14 @@
                << ": expected Pointer to be of type OpTypePointer";
       }
 
+      // Can't use result_type because OpAtomicStore doesn't have a result
+      if ( _.IsIntScalarType(data_type) &&_.GetBitWidth(data_type) == 64 &&
+          !_.HasCapability(SpvCapabilityInt64Atomics)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << spvOpcodeString(opcode)
+               << ": 64-bit atomics require the Int64Atomics capability";
+      }
+
       // Validate storage class against universal rules
       if (!IsStorageClassAllowedByUniversalRules(storage_class)) {
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
@@ -213,6 +200,7 @@
 
       // Then Shader rules
       if (_.HasCapability(SpvCapabilityShader)) {
+        // Vulkan environment rule
         if (spvIsVulkanEnv(_.context()->target_env)) {
           if ((storage_class != SpvStorageClassUniform) &&
               (storage_class != SpvStorageClassStorageBuffer) &&
@@ -231,6 +219,54 @@
                  << ": Function storage class forbidden when the Shader "
                     "capability is declared.";
         }
+
+        if (opcode == SpvOpAtomicFAddEXT) {
+          // result type being float checked already
+          if ((_.GetBitWidth(result_type) == 16) &&
+              (!_.HasCapability(SpvCapabilityAtomicFloat16AddEXT))) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << spvOpcodeString(opcode)
+                   << ": float add atomics require the AtomicFloat32AddEXT "
+                      "capability";
+          }
+          if ((_.GetBitWidth(result_type) == 32) &&
+              (!_.HasCapability(SpvCapabilityAtomicFloat32AddEXT))) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << spvOpcodeString(opcode)
+                   << ": float add atomics require the AtomicFloat32AddEXT "
+                      "capability";
+          }
+          if ((_.GetBitWidth(result_type) == 64) &&
+              (!_.HasCapability(SpvCapabilityAtomicFloat64AddEXT))) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << spvOpcodeString(opcode)
+                   << ": float add atomics require the AtomicFloat64AddEXT "
+                      "capability";
+          }
+        } else if (opcode == SpvOpAtomicFMinEXT ||
+                   opcode == SpvOpAtomicFMaxEXT) {
+          if ((_.GetBitWidth(result_type) == 16) &&
+              (!_.HasCapability(SpvCapabilityAtomicFloat16MinMaxEXT))) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << spvOpcodeString(opcode)
+                   << ": float min/max atomics require the "
+                      "AtomicFloat16MinMaxEXT capability";
+          }
+          if ((_.GetBitWidth(result_type) == 32) &&
+              (!_.HasCapability(SpvCapabilityAtomicFloat32MinMaxEXT))) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << spvOpcodeString(opcode)
+                   << ": float min/max atomics require the "
+                      "AtomicFloat32MinMaxEXT capability";
+          }
+          if ((_.GetBitWidth(result_type) == 64) &&
+              (!_.HasCapability(SpvCapabilityAtomicFloat64MinMaxEXT))) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << spvOpcodeString(opcode)
+                   << ": float min/max atomics require the "
+                      "AtomicFloat64MinMaxEXT capability";
+          }
+        }
       }
 
       // And finally OpenCL environment rules
@@ -254,27 +290,27 @@
         }
       }
 
+      // If result and pointer type are different, need to do special check here
       if (opcode == SpvOpAtomicFlagTestAndSet ||
           opcode == SpvOpAtomicFlagClear) {
         if (!_.IsIntScalarType(data_type) || _.GetBitWidth(data_type) != 32) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << spvOpcodeString(opcode)
-                 << ": expected Pointer to point to a value of 32-bit int type";
+                 << ": expected Pointer to point to a value of 32-bit integer "
+                    "type";
         }
       } else if (opcode == SpvOpAtomicStore) {
         if (!_.IsFloatScalarType(data_type) && !_.IsIntScalarType(data_type)) {
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
                  << spvOpcodeString(opcode)
-                 << ": expected Pointer to be a pointer to int or float "
+                 << ": expected Pointer to be a pointer to integer or float "
                  << "scalar type";
         }
-      } else {
-        if (data_type != result_type) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << spvOpcodeString(opcode)
-                 << ": expected Pointer to point to a value of type Result "
-                    "Type";
-        }
+      } else if (data_type != result_type) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << spvOpcodeString(opcode)
+               << ": expected Pointer to point to a value of type Result "
+                  "Type";
       }
 
       auto memory_scope = inst->GetOperandAs<const uint32_t>(operand_index++);
@@ -283,14 +319,15 @@
       }
 
       const auto equal_semantics_index = operand_index++;
-      if (auto error = ValidateMemorySemantics(_, inst, equal_semantics_index))
+      if (auto error = ValidateMemorySemantics(_, inst, equal_semantics_index,
+                                               memory_scope))
         return error;
 
       if (opcode == SpvOpAtomicCompareExchange ||
           opcode == SpvOpAtomicCompareExchangeWeak) {
         const auto unequal_semantics_index = operand_index++;
-        if (auto error =
-                ValidateMemorySemantics(_, inst, unequal_semantics_index))
+        if (auto error = ValidateMemorySemantics(
+                _, inst, unequal_semantics_index, memory_scope))
           return error;
 
         // Volatile bits must match for equal and unequal semantics. Previous
diff --git a/source/val/validate_barriers.cpp b/source/val/validate_barriers.cpp
index b499c8c..3a9e3e7 100644
--- a/source/val/validate_barriers.cpp
+++ b/source/val/validate_barriers.cpp
@@ -69,7 +69,7 @@
         return error;
       }
 
-      if (auto error = ValidateMemorySemantics(_, inst, 2)) {
+      if (auto error = ValidateMemorySemantics(_, inst, 2, memory_scope)) {
         return error;
       }
       break;
@@ -82,7 +82,7 @@
         return error;
       }
 
-      if (auto error = ValidateMemorySemantics(_, inst, 1)) {
+      if (auto error = ValidateMemorySemantics(_, inst, 1, memory_scope)) {
         return error;
       }
       break;
@@ -119,7 +119,7 @@
         return error;
       }
 
-      if (auto error = ValidateMemorySemantics(_, inst, 2)) {
+      if (auto error = ValidateMemorySemantics(_, inst, 2, memory_scope)) {
         return error;
       }
       break;
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index 3c9df9f..57dde8a 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -2846,7 +2846,7 @@
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, builtin)
-               << " to be used only with GLCompute execution model. "
+               << " to be used only with GLCompute, MeshNV, or TaskNV execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
       }
@@ -2928,7 +2928,7 @@
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, builtin)
-               << " to be used only with GLCompute execution model. "
+               << " to be used only with GLCompute, MeshNV, or TaskNV execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
       }
@@ -3070,14 +3070,16 @@
     const Instruction& referenced_from_inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
     for (const SpvExecutionModel execution_model : execution_models_) {
-      if (execution_model != SpvExecutionModelGLCompute) {
+      if (execution_model != SpvExecutionModelGLCompute &&
+          execution_model != SpvExecutionModelTaskNV &&
+          execution_model != SpvExecutionModelMeshNV) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
                << _.VkErrorID(4425)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                                 decoration.params()[0])
-               << " to be used only with GLCompute execution model. "
+               << " to be used only with GLCompute, MeshNV, or TaskNV execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
                                    referenced_from_inst, execution_model);
       }
@@ -3991,14 +3993,6 @@
     const Decoration& decoration, const Instruction& inst) {
   const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]);
 
-  // Builtins can only be applied to variables, structures or constants.
-  auto target_opcode = inst.opcode();
-  if (target_opcode != SpvOpTypeStruct && target_opcode != SpvOpVariable &&
-      !spvOpcodeIsConstant(target_opcode)) {
-    return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-           << "BuiltIns can only target variables, structs or constants";
-  }
-
   if (!spvIsVulkanEnv(_.context()->target_env)) {
     // Early return. All currently implemented rules are based on Vulkan spec.
     //
@@ -4190,6 +4184,7 @@
     case SpvBuiltInMeshViewIndicesNV:
     case SpvBuiltInBaryCoordNV:
     case SpvBuiltInBaryCoordNoPerspNV:
+    case SpvBuiltInCurrentRayTimeNV:
       // No validation rules (for the moment).
       break;
 
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index a5f6e6a..7842e56 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -199,6 +199,18 @@
   // At least two operands (selector, default), any more than that are
   // literal/target.
 
+  const auto sel_type_id = _.GetOperandTypeId(inst, 0);
+  if (!_.IsIntScalarType(sel_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Selector type must be OpTypeInt";
+  }
+
+  const auto default_label = _.FindDef(inst->GetOperandAs<uint32_t>(1));
+  if (default_label->opcode() != SpvOpLabel) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Default must be an OpLabel instruction";
+  }
+
   // target operands must be OpLabel
   for (size_t i = 2; i < num_operands; i += 2) {
     // literal, id
@@ -647,25 +659,22 @@
       // Mark the upcoming blocks as seen now, but only error out if this block
       // was missing a merge instruction and both labels hadn't been seen
       // previously.
-      const bool both_unseen =
-          seen.insert(true_label).second && seen.insert(false_label).second;
-      if (!merge && both_unseen) {
+      const bool true_label_unseen = seen.insert(true_label).second;
+      const bool false_label_unseen = seen.insert(false_label).second;
+      if (!merge && true_label_unseen && false_label_unseen) {
         return _.diag(SPV_ERROR_INVALID_CFG, terminator)
                << "Selection must be structured";
       }
     } else if (terminator->opcode() == SpvOpSwitch) {
-      uint32_t count = 0;
-      // Mark the targets as seen now, but only error out if this block was
-      // missing a merge instruction and there were multiple unseen labels.
+      if (!merge) {
+        return _.diag(SPV_ERROR_INVALID_CFG, terminator)
+               << "OpSwitch must be preceeded by an OpSelectionMerge "
+                  "instruction";
+      }
+      // Mark the targets as seen.
       for (uint32_t i = 1; i < terminator->operands().size(); i += 2) {
         const auto target = terminator->GetOperandAs<uint32_t>(i);
-        if (seen.insert(target).second) {
-          count++;
-        }
-      }
-      if (!merge && count > 1) {
-        return _.diag(SPV_ERROR_INVALID_CFG, terminator)
-               << "Selection must be structured";
+        seen.insert(target);
       }
     }
   }
diff --git a/source/val/validate_conversion.cpp b/source/val/validate_conversion.cpp
index 0060d0b..b4e39cf 100644
--- a/source/val/validate_conversion.cpp
+++ b/source/val/validate_conversion.cpp
@@ -14,12 +14,12 @@
 
 // Validates correctness of conversion instructions.
 
-#include "source/val/validate.h"
-
 #include "source/diagnostic.h"
 #include "source/opcode.h"
 #include "source/spirv_constant.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 {
@@ -263,16 +263,25 @@
                << "Logical addressing not supported: "
                << spvOpcodeString(opcode);
 
-      if (_.addressing_model() ==
-          SpvAddressingModelPhysicalStorageBuffer64EXT) {
+      if (_.addressing_model() == SpvAddressingModelPhysicalStorageBuffer64) {
         uint32_t input_storage_class = 0;
         uint32_t input_data_type = 0;
         _.GetPointerTypeInfo(input_type, &input_data_type,
                              &input_storage_class);
-        if (input_storage_class != SpvStorageClassPhysicalStorageBufferEXT)
+        if (input_storage_class != SpvStorageClassPhysicalStorageBuffer)
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << "Pointer storage class must be PhysicalStorageBufferEXT: "
+                 << "Pointer storage class must be PhysicalStorageBuffer: "
                  << spvOpcodeString(opcode);
+
+        if (spvIsVulkanEnv(_.context()->target_env)) {
+          if (_.GetBitWidth(result_type) != 64) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << _.VkErrorID(4710)
+                   << "PhysicalStorageBuffer64 addressing mode requires the "
+                      "result integer type to have a 64-bit width for Vulkan "
+                      "environment.";
+          }
+        }
       }
       break;
     }
@@ -314,16 +323,25 @@
                << "Logical addressing not supported: "
                << spvOpcodeString(opcode);
 
-      if (_.addressing_model() ==
-          SpvAddressingModelPhysicalStorageBuffer64EXT) {
+      if (_.addressing_model() == SpvAddressingModelPhysicalStorageBuffer64) {
         uint32_t result_storage_class = 0;
         uint32_t result_data_type = 0;
         _.GetPointerTypeInfo(result_type, &result_data_type,
                              &result_storage_class);
-        if (result_storage_class != SpvStorageClassPhysicalStorageBufferEXT)
+        if (result_storage_class != SpvStorageClassPhysicalStorageBuffer)
           return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << "Pointer storage class must be PhysicalStorageBufferEXT: "
+                 << "Pointer storage class must be PhysicalStorageBuffer: "
                  << spvOpcodeString(opcode);
+
+        if (spvIsVulkanEnv(_.context()->target_env)) {
+          if (_.GetBitWidth(input_type) != 64) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << _.VkErrorID(4710)
+                   << "PhysicalStorageBuffer64 addressing mode requires the "
+                      "input integer to have a 64-bit width for Vulkan "
+                      "environment.";
+          }
+        }
       }
       break;
     }
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index 01b0eca..3cdb471 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -129,18 +129,30 @@
 // Returns whether the given structure is missing Offset decoration for any
 // member. Handles also nested structures.
 bool isMissingOffsetInStruct(uint32_t struct_id, ValidationState_t& vstate) {
-  std::vector<bool> hasOffset(getStructMembers(struct_id, vstate).size(),
-                              false);
-  // Check offsets of member decorations
-  for (auto& decoration : vstate.id_decorations(struct_id)) {
-    if (SpvDecorationOffset == decoration.dec_type() &&
-        Decoration::kInvalidMember != decoration.struct_member_index()) {
-      hasOffset[decoration.struct_member_index()] = true;
+  const auto* inst = vstate.FindDef(struct_id);
+  std::vector<bool> hasOffset;
+  std::vector<uint32_t> struct_members;
+  if (inst->opcode() == SpvOpTypeStruct) {
+    // Check offsets of member decorations.
+    struct_members = getStructMembers(struct_id, vstate);
+    hasOffset.resize(struct_members.size(), false);
+
+    for (auto& decoration : vstate.id_decorations(struct_id)) {
+      if (SpvDecorationOffset == decoration.dec_type() &&
+          Decoration::kInvalidMember != decoration.struct_member_index()) {
+        // Offset 0xffffffff is not valid so ignore it for simplicity's sake.
+        if (decoration.params()[0] == 0xffffffff) return true;
+        hasOffset[decoration.struct_member_index()] = true;
+      }
     }
+  } else if (inst->opcode() == SpvOpTypeArray ||
+             inst->opcode() == SpvOpTypeRuntimeArray) {
+    hasOffset.resize(1, true);
+    struct_members.push_back(inst->GetOperandAs<uint32_t>(1u));
   }
-  // Check also nested structures
+  // Look through nested structs (which may be in an array).
   bool nestedStructsMissingOffset = false;
-  for (auto id : getStructMembers(struct_id, SpvOpTypeStruct, vstate)) {
+  for (auto id : struct_members) {
     if (isMissingOffsetInStruct(id, vstate)) {
       nestedStructsMissingOffset = true;
       break;
@@ -379,6 +391,7 @@
 // or row major-ness.
 spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
                          const char* decoration_str, bool blockRules,
+                         bool scalar_block_layout,
                          uint32_t incoming_offset,
                          MemberConstraints& constraints,
                          ValidationState_t& vstate) {
@@ -392,7 +405,6 @@
   // For example, relaxed layout is implied by Vulkan 1.1.  But scalar layout
   // is more permissive than relaxed layout.
   const bool relaxed_block_layout = vstate.IsRelaxedBlockLayout();
-  const bool scalar_block_layout = vstate.options()->scalar_block_layout;
 
   auto fail = [&vstate, struct_id, storage_class_str, decoration_str,
                blockRules, relaxed_block_layout,
@@ -501,16 +513,15 @@
     if (SpvOpTypeStruct == opcode &&
         SPV_SUCCESS != (recursive_status = checkLayout(
                             id, storage_class_str, decoration_str, blockRules,
+                            scalar_block_layout,
                             offset, constraints, vstate)))
       return recursive_status;
     // Check matrix stride.
     if (SpvOpTypeMatrix == opcode) {
-      for (auto& decoration : vstate.id_decorations(id)) {
-        if (SpvDecorationMatrixStride == decoration.dec_type() &&
-            !IsAlignedTo(decoration.params()[0], alignment))
-          return fail(memberIdx)
-                 << "is a matrix with stride " << decoration.params()[0]
-                 << " not satisfying alignment to " << alignment;
+      const auto stride = constraint.matrix_stride;
+      if (!IsAlignedTo(stride, alignment)) {
+        return fail(memberIdx) << "is a matrix with stride " << stride
+                               << " not satisfying alignment to " << alignment;
       }
     }
 
@@ -548,16 +559,23 @@
       // 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;
+      if (SpvOpTypeStruct == element_inst->opcode()) {
+        std::vector<bool> seen(16, false);
+        for (uint32_t i = 0; i < num_elements; ++i) {
+          uint32_t next_offset = i * array_stride + offset;
+          // Stop checking if offsets repeat in terms of 16-byte multiples.
+          if (seen[next_offset % 16]) {
+            break;
+          }
+
+          if (SPV_SUCCESS !=
+              (recursive_status = checkLayout(
+                   typeId, storage_class_str, decoration_str, blockRules,
+                   scalar_block_layout, next_offset, constraints, vstate)))
+            return recursive_status;
+
+          seen[next_offset % 16] = true;
+        }
       }
 
       // Proceed to the element in case it is an array.
@@ -698,6 +716,9 @@
     const auto& descs = vstate.entry_point_descriptions(entry_point);
     int num_builtin_inputs = 0;
     int num_builtin_outputs = 0;
+    int num_workgroup_variables = 0;
+    int num_workgroup_variables_with_block = 0;
+    int num_workgroup_variables_with_aliased = 0;
     for (const auto& desc : descs) {
       std::unordered_set<Instruction*> seen_vars;
       for (auto interface : desc.interfaces) {
@@ -754,6 +775,16 @@
           if (auto error = CheckBuiltInVariable(interface, vstate))
             return error;
         }
+
+        if (storage_class == SpvStorageClassWorkgroup) {
+          ++num_workgroup_variables;
+          if (type_instr && SpvOpTypeStruct == type_instr->opcode()) {
+            if (hasDecoration(type_id, SpvDecorationBlock, vstate))
+              ++num_workgroup_variables_with_block;
+            if (hasDecoration(var_instr->id(), SpvDecorationAliased, vstate))
+              ++num_workgroup_variables_with_aliased;
+          }
+        }
       }
       if (num_builtin_inputs > 1 || num_builtin_outputs > 1) {
         return vstate.diag(SPV_ERROR_INVALID_BINARY,
@@ -777,6 +808,30 @@
                  << " because it is targeted by an OpEntryPoint instruction.";
         }
       }
+
+      if (vstate.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayoutKHR) &&
+          num_workgroup_variables > 0 &&
+          num_workgroup_variables_with_block > 0) {
+        if (num_workgroup_variables != num_workgroup_variables_with_block) {
+          return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
+                 << "When declaring WorkgroupMemoryExplicitLayoutKHR, "
+                    "either all or none of the Workgroup Storage Class variables "
+                    "in the entry point interface must point to struct types "
+                    "decorated with Block.  Entry point id "
+                 << entry_point << " does not meet this requirement.";
+        }
+        if (num_workgroup_variables_with_block > 1 &&
+            num_workgroup_variables_with_block !=
+            num_workgroup_variables_with_aliased) {
+          return vstate.diag(SPV_ERROR_INVALID_BINARY, vstate.FindDef(entry_point))
+                 << "When declaring WorkgroupMemoryExplicitLayoutKHR, "
+                    "if more than one Workgroup Storage Class variable in "
+                    "the entry point interface point to a type decorated "
+                    "with Block, all of them must be decorated with Aliased. "
+                    "Entry point id "
+                 << entry_point << " does not meet this requirement.";
+        }
+      }
     }
   }
   return SPV_SUCCESS;
@@ -942,14 +997,18 @@
 
       const bool phys_storage_buffer =
           storageClass == SpvStorageClassPhysicalStorageBufferEXT;
-      if (uniform || push_constant || storage_buffer || phys_storage_buffer) {
+      const bool workgroup =
+          storageClass == SpvStorageClassWorkgroup &&
+          vstate.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayoutKHR);
+      if (uniform || push_constant || storage_buffer || phys_storage_buffer ||
+          workgroup) {
         const auto ptrInst = vstate.FindDef(words[1]);
         assert(SpvOpTypePointer == ptrInst->opcode());
         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) {
+        if (!workgroup && (id_inst->opcode() == SpvOpTypeArray ||
+                           id_inst->opcode() == SpvOpTypeRuntimeArray)) {
           id = id_inst->GetOperandAs<uint32_t>(1u);
           id_inst = vstate.FindDef(id);
         }
@@ -961,7 +1020,9 @@
         // Prepare for messages
         const char* sc_str =
             uniform ? "Uniform"
-                    : (push_constant ? "PushConstant" : "StorageBuffer");
+                    : (push_constant ? "PushConstant"
+                                     : (workgroup ? "Workgroup"
+                                                  : "StorageBuffer"));
 
         if (spvIsVulkanEnv(vstate.context()->target_env)) {
           const bool block = hasDecoration(id, SpvDecorationBlock, vstate);
@@ -1029,8 +1090,9 @@
           const bool bufferDeco = SpvDecorationBufferBlock == dec.dec_type();
           const bool blockRules = uniform && blockDeco;
           const bool bufferRules =
-              (uniform && bufferDeco) || (push_constant && blockDeco) ||
-              ((storage_buffer || phys_storage_buffer) && blockDeco);
+              (uniform && bufferDeco) ||
+              ((push_constant || storage_buffer ||
+                phys_storage_buffer || workgroup) && blockDeco);
           if (uniform && blockDeco) {
             vstate.RegisterPointerToUniformBlock(ptrInst->id());
             vstate.RegisterStructForUniformBlock(id);
@@ -1044,6 +1106,10 @@
           if (blockRules || bufferRules) {
             const char* deco_str = blockDeco ? "Block" : "BufferBlock";
             spv_result_t recursive_status = SPV_SUCCESS;
+            const bool scalar_block_layout = workgroup ?
+                vstate.options()->workgroup_scalar_block_layout :
+                vstate.options()->scalar_block_layout;
+
             if (isMissingOffsetInStruct(id, vstate)) {
               return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(id))
                      << "Structure id " << id << " decorated as " << deco_str
@@ -1072,12 +1138,14 @@
                         "decorations.";
             } else if (blockRules &&
                        (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, true, 0,
+                                            id, sc_str, deco_str, true,
+                                            scalar_block_layout, 0,
                                             constraints, vstate)))) {
               return recursive_status;
             } else if (bufferRules &&
                        (SPV_SUCCESS != (recursive_status = checkLayout(
-                                            id, sc_str, deco_str, false, 0,
+                                            id, sc_str, deco_str, false,
+                                            scalar_block_layout, 0,
                                             constraints, vstate)))) {
               return recursive_status;
             }
@@ -1569,7 +1637,7 @@
   {                                             \
     spv_result_t e##LINE = (X);                 \
     if (e##LINE != SPV_SUCCESS) return e##LINE; \
-  }
+  } static_assert(true, "require extra semicolon")
 #define PASS_OR_BAIL(X) PASS_OR_BAIL_AT_LINE(X, __LINE__)
 
 // Check rules for decorations where we start from the decoration rather
diff --git a/source/val/validate_derivatives.cpp b/source/val/validate_derivatives.cpp
index 067cc96..25b941a 100644
--- a/source/val/validate_derivatives.cpp
+++ b/source/val/validate_derivatives.cpp
@@ -79,11 +79,13 @@
                                         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 (models &&
+                models->find(SpvExecutionModelGLCompute) != models->end() &&
+                (!modes ||
+                 (modes->find(SpvExecutionModeDerivativeGroupLinearNV) ==
+                      modes->end() &&
+                  modes->find(SpvExecutionModeDerivativeGroupQuadsNV) ==
+                      modes->end()))) {
               if (message) {
                 *message = std::string(
                                "Derivative instructions require "
diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp
index af6ae2b..dccbe14 100644
--- a/source/val/validate_extensions.cpp
+++ b/source/val/validate_extensions.cpp
@@ -20,13 +20,16 @@
 
 #include "spirv/unified1/NonSemanticClspvReflection.h"
 
+#include "NonSemanticShaderDebugInfo100.h"
 #include "OpenCLDebugInfo100.h"
+#include "source/common_debug_info.h"
 #include "source/diagnostic.h"
 #include "source/enum_string_mapping.h"
 #include "source/extensions.h"
 #include "source/latest_version_glsl_std_450_header.h"
 #include "source/latest_version_opencl_std_header.h"
 #include "source/opcode.h"
+#include "source/spirv_constant.h"
 #include "source/spirv_target_env.h"
 #include "source/val/instruction.h"
 #include "source/val/validate.h"
@@ -44,6 +47,34 @@
   return 0;
 }
 
+bool IsIntScalar(ValidationState_t& _, uint32_t id, bool must_len32,
+                 bool must_unsigned) {
+  auto type = _.FindDef(id);
+  if (!type || type->opcode() != SpvOpTypeInt) {
+    return false;
+  }
+
+  if (must_len32 && type->GetOperandAs<uint32_t>(1) != 32) {
+    return false;
+  }
+
+  return !must_unsigned || type->GetOperandAs<uint32_t>(2) == 0;
+}
+
+bool IsUint32Constant(ValidationState_t& _, uint32_t id) {
+  auto inst = _.FindDef(id);
+  if (!inst || inst->opcode() != SpvOpConstant) {
+    return false;
+  }
+
+  return IsIntScalar(_, inst->type_id(), true, true);
+}
+
+uint32_t GetUint32Constant(ValidationState_t& _, uint32_t id) {
+  auto inst = _.FindDef(id);
+  return inst->word(3);
+}
+
 // Check that the operand of a debug info instruction |inst| at |word_index|
 // is a result id of an instruction with |expected_opcode|.
 spv_result_t ValidateOperandForDebugInfo(
@@ -67,6 +98,22 @@
   return SPV_SUCCESS;
 }
 
+// For NonSemantic.Shader.DebugInfo.100 check that the operand of a debug info
+// instruction |inst| at |word_index| is a result id of a 32-bit integer
+// OpConstant instruction. For OpenCL.DebugInfo.100 the parameter is a literal
+// word so cannot be validated.
+spv_result_t ValidateUint32ConstantOperandForDebugInfo(
+    ValidationState_t& _, const std::string& operand_name,
+    const Instruction* inst, uint32_t word_index,
+    const std::function<std::string()>& ext_inst_name) {
+  if (!IsUint32Constant(_, inst->word(word_index))) {
+    return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << ext_inst_name() << ": expected operand " << operand_name
+           << " must be a result id of 32-bit unsigned OpConstant";
+  }
+  return SPV_SUCCESS;
+}
+
 #define CHECK_OPERAND(NAME, opcode, index)                                  \
   do {                                                                      \
     auto result = ValidateOperandForDebugInfo(_, NAME, opcode, inst, index, \
@@ -74,18 +121,27 @@
     if (result != SPV_SUCCESS) return result;                               \
   } while (0)
 
+#define CHECK_CONST_UINT_OPERAND(NAME, index)                \
+  if (vulkanDebugInfo) {                                     \
+    auto result = ValidateUint32ConstantOperandForDebugInfo( \
+        _, NAME, inst, index, ext_inst_name);                \
+    if (result != SPV_SUCCESS) return result;                \
+  }
+
 // True if the operand of a debug info instruction |inst| at |word_index|
 // satisifies |expectation| that is given as a function. Otherwise,
 // returns false.
 bool DoesDebugInfoOperandMatchExpectation(
     const ValidationState_t& _,
-    const std::function<bool(OpenCLDebugInfo100Instructions)>& expectation,
+    const std::function<bool(CommonDebugInfoInstructions)>& expectation,
     const Instruction* inst, uint32_t word_index) {
   if (inst->words().size() <= word_index) return false;
   auto* debug_inst = _.FindDef(inst->word(word_index));
   if (debug_inst->opcode() != SpvOpExtInst ||
-      debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
-      !expectation(OpenCLDebugInfo100Instructions(debug_inst->word(4)))) {
+      (debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 &&
+       debug_inst->ext_inst_type() !=
+           SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) ||
+      !expectation(CommonDebugInfoInstructions(debug_inst->word(4)))) {
     return false;
   }
   return true;
@@ -96,20 +152,18 @@
 // is |expected_debug_inst|.
 spv_result_t ValidateDebugInfoOperand(
     ValidationState_t& _, const std::string& debug_inst_name,
-    OpenCLDebugInfo100Instructions expected_debug_inst, const Instruction* inst,
+    CommonDebugInfoInstructions expected_debug_inst, const Instruction* inst,
     uint32_t word_index, const std::function<std::string()>& ext_inst_name) {
-  std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
-      [expected_debug_inst](OpenCLDebugInfo100Instructions dbg_inst) {
+  std::function<bool(CommonDebugInfoInstructions)> expectation =
+      [expected_debug_inst](CommonDebugInfoInstructions dbg_inst) {
         return dbg_inst == expected_debug_inst;
       };
   if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
     return SPV_SUCCESS;
 
   spv_ext_inst_desc desc = nullptr;
-  _.grammar().lookupExtInst(SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100,
-                            expected_debug_inst, &desc);
-  if (_.grammar().lookupExtInst(SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100,
-                                expected_debug_inst, &desc) != SPV_SUCCESS ||
+  if (_.grammar().lookupExtInst(inst->ext_inst_type(), expected_debug_inst,
+                                &desc) != SPV_SUCCESS ||
       !desc) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
            << ext_inst_name() << ": "
@@ -133,9 +187,8 @@
 spv_result_t ValidateOperandBaseType(
     ValidationState_t& _, const Instruction* inst, uint32_t word_index,
     const std::function<std::string()>& ext_inst_name) {
-  return ValidateDebugInfoOperand(_, "Base Type",
-                                  OpenCLDebugInfo100DebugTypeBasic, inst,
-                                  word_index, ext_inst_name);
+  return ValidateDebugInfoOperand(_, "Base Type", CommonDebugInfoDebugTypeBasic,
+                                  inst, word_index, ext_inst_name);
 }
 
 // Check that the operand of a debug info instruction |inst| at |word_index|
@@ -146,12 +199,12 @@
     ValidationState_t& _, const std::string& debug_inst_name,
     const Instruction* inst, uint32_t word_index,
     const std::function<std::string()>& ext_inst_name) {
-  std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
-      [](OpenCLDebugInfo100Instructions dbg_inst) {
-        return dbg_inst == OpenCLDebugInfo100DebugCompilationUnit ||
-               dbg_inst == OpenCLDebugInfo100DebugFunction ||
-               dbg_inst == OpenCLDebugInfo100DebugLexicalBlock ||
-               dbg_inst == OpenCLDebugInfo100DebugTypeComposite;
+  std::function<bool(CommonDebugInfoInstructions)> expectation =
+      [](CommonDebugInfoInstructions dbg_inst) {
+        return dbg_inst == CommonDebugInfoDebugCompilationUnit ||
+               dbg_inst == CommonDebugInfoDebugFunction ||
+               dbg_inst == CommonDebugInfoDebugLexicalBlock ||
+               dbg_inst == CommonDebugInfoDebugTypeComposite;
       };
   if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
     return SPV_SUCCESS;
@@ -170,16 +223,15 @@
     const Instruction* inst, uint32_t word_index,
     const std::function<std::string()>& ext_inst_name,
     bool allow_template_param) {
-  std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
-      [&allow_template_param](OpenCLDebugInfo100Instructions dbg_inst) {
+  std::function<bool(CommonDebugInfoInstructions)> expectation =
+      [&allow_template_param](CommonDebugInfoInstructions dbg_inst) {
         if (allow_template_param &&
-            (dbg_inst == OpenCLDebugInfo100DebugTypeTemplateParameter ||
-             dbg_inst ==
-                 OpenCLDebugInfo100DebugTypeTemplateTemplateParameter)) {
+            (dbg_inst == CommonDebugInfoDebugTypeTemplateParameter ||
+             dbg_inst == CommonDebugInfoDebugTypeTemplateTemplateParameter)) {
           return true;
         }
-        return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst &&
-               dbg_inst <= OpenCLDebugInfo100DebugTypeTemplate;
+        return CommonDebugInfoDebugTypeBasic <= dbg_inst &&
+               dbg_inst <= CommonDebugInfoDebugTypeTemplate;
       };
   if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
     return SPV_SUCCESS;
@@ -190,28 +242,6 @@
          << " is not a valid debug type";
 }
 
-bool IsUint32Constant(ValidationState_t& _, uint32_t id) {
-  auto inst = _.FindDef(id);
-  if (!inst || inst->opcode() != SpvOpConstant) {
-    return false;
-  }
-
-  auto type = _.FindDef(inst->type_id());
-  if (!type || type->opcode() != SpvOpTypeInt) {
-    return false;
-  }
-
-  if (type->GetOperandAs<uint32_t>(1) != 32) {
-    return false;
-  }
-
-  if (type->GetOperandAs<uint32_t>(2) != 0) {
-    return false;
-  }
-
-  return true;
-}
-
 spv_result_t ValidateClspvReflectionKernel(ValidationState_t& _,
                                            const Instruction* inst) {
   const auto kernel_id = inst->GetOperandAs<uint32_t>(4);
@@ -666,18 +696,26 @@
                                       const Instruction* inst,
                                       uint32_t word_index) {
   auto* dbg_int_scalar_var = _.FindDef(inst->word(word_index));
-  if (OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) ==
-          OpenCLDebugInfo100DebugLocalVariable ||
-      OpenCLDebugInfo100Instructions(dbg_int_scalar_var->word(4)) ==
-          OpenCLDebugInfo100DebugGlobalVariable) {
+  if (CommonDebugInfoInstructions(dbg_int_scalar_var->word(4)) ==
+          CommonDebugInfoDebugLocalVariable ||
+      CommonDebugInfoInstructions(dbg_int_scalar_var->word(4)) ==
+          CommonDebugInfoDebugGlobalVariable) {
     auto* dbg_type = _.FindDef(dbg_int_scalar_var->word(6));
-    if (OpenCLDebugInfo100Instructions(dbg_type->word(4)) ==
-            OpenCLDebugInfo100DebugTypeBasic &&
-        (OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) ==
-             OpenCLDebugInfo100Signed ||
-         OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(dbg_type->word(7)) ==
-             OpenCLDebugInfo100Unsigned)) {
-      return true;
+    if (CommonDebugInfoInstructions(dbg_type->word(4)) ==
+        CommonDebugInfoDebugTypeBasic) {
+      const spv_ext_inst_type_t ext_inst_type =
+          spv_ext_inst_type_t(inst->ext_inst_type());
+      const bool vulkanDebugInfo =
+          ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100;
+      uint32_t encoding = dbg_type->word(7);
+      if (!vulkanDebugInfo || IsUint32Constant(_, encoding)) {
+        auto ocl_encoding = OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(
+            vulkanDebugInfo ? GetUint32Constant(_, encoding) : encoding);
+        if (ocl_encoding == OpenCLDebugInfo100Signed ||
+            ocl_encoding == OpenCLDebugInfo100Unsigned) {
+          return true;
+        }
+      }
     }
   }
   return false;
@@ -685,6 +723,20 @@
 
 }  // anonymous namespace
 
+spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) {
+  if (_.version() < SPV_SPIRV_VERSION_WORD(1, 4)) {
+    std::string extension = GetExtensionString(&(inst->c_inst()));
+    if (extension ==
+        ExtensionToString(kSPV_KHR_workgroup_memory_explicit_layout)) {
+      return _.diag(SPV_ERROR_WRONG_VERSION, inst)
+             << "SPV_KHR_workgroup_memory_explicit_layout extension "
+                "requires SPIR-V version 1.4 or later.";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t ValidateExtInstImport(ValidationState_t& _,
                                    const Instruction* inst) {
   const auto name_id = 1;
@@ -797,7 +849,7 @@
         for (uint32_t operand_index = 4; operand_index < num_operands;
              ++operand_index) {
           const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index);
-          if (!_.IsIntScalarOrVectorType(operand_type)) {
+          if (!operand_type || !_.IsIntScalarOrVectorType(operand_type)) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
                    << ext_inst_name() << ": "
                    << "expected all operands to be int scalars or vectors";
@@ -1357,7 +1409,16 @@
                  << "or vector type";
         }
 
-        const uint32_t interpolant_type = _.GetOperandTypeId(inst, 4);
+        // If HLSL legalization and first operand is an OpLoad, use load
+        // pointer as the interpolant lvalue. Else use interpolate first
+        // operand.
+        uint32_t interp_id = inst->GetOperandAs<uint32_t>(4);
+        auto* interp_inst = _.FindDef(interp_id);
+        uint32_t interpolant_type = (_.options()->before_hlsl_legalization &&
+                                     interp_inst->opcode() == SpvOpLoad)
+                                        ? _.GetOperandTypeId(interp_inst, 2)
+                                        : _.GetOperandTypeId(inst, 4);
+
         uint32_t interpolant_storage_class = 0;
         uint32_t interpolant_data_type = 0;
         if (!_.GetPointerTypeInfo(interpolant_type, &interpolant_data_type,
@@ -2644,7 +2705,9 @@
         break;
       }
     }
-  } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
+  } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
+             ext_inst_type ==
+                 SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
     if (!_.IsVoidType(result_type)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << ext_inst_name() << ": "
@@ -2652,446 +2715,552 @@
              << "OpTypeVoid";
     }
 
+    const bool vulkanDebugInfo =
+        ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100;
+
     auto num_words = inst->words().size();
 
-    const OpenCLDebugInfo100Instructions ext_inst_key =
-        OpenCLDebugInfo100Instructions(ext_inst_index);
-    switch (ext_inst_key) {
-      case OpenCLDebugInfo100DebugInfoNone:
-      case OpenCLDebugInfo100DebugNoScope:
-      case OpenCLDebugInfo100DebugOperation:
-        // The binary parser validates the opcode for DebugInfoNone,
-        // DebugNoScope, DebugOperation, and the literal values don't need
-        // further checks.
-        break;
-      case OpenCLDebugInfo100DebugCompilationUnit: {
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        break;
-      }
-      case OpenCLDebugInfo100DebugSource: {
-        CHECK_OPERAND("File", SpvOpString, 5);
-        if (num_words == 7) CHECK_OPERAND("Text", SpvOpString, 6);
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeBasic: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        CHECK_OPERAND("Size", SpvOpConstant, 6);
-        // "Encoding" param is already validated by the binary parsing stage.
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypePointer:
-      case OpenCLDebugInfo100DebugTypeQualifier: {
-        auto validate_base_type =
-            ValidateOperandBaseType(_, inst, 5, ext_inst_name);
-        if (validate_base_type != SPV_SUCCESS) return validate_base_type;
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeVector: {
-        auto validate_base_type =
-            ValidateOperandBaseType(_, inst, 5, ext_inst_name);
-        if (validate_base_type != SPV_SUCCESS) return validate_base_type;
-
-        uint32_t component_count = inst->word(6);
-        if (!component_count || component_count > 4) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << ext_inst_name() << ": Component Count must be positive "
-                 << "integer less than or equal to 4";
+    // Handle any non-common OpenCL insts, then common
+    if (ext_inst_type != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
+        OpenCLDebugInfo100Instructions(ext_inst_index) !=
+            OpenCLDebugInfo100DebugModuleINTEL) {
+      const CommonDebugInfoInstructions ext_inst_key =
+          CommonDebugInfoInstructions(ext_inst_index);
+      switch (ext_inst_key) {
+        case CommonDebugInfoDebugInfoNone:
+        case CommonDebugInfoDebugNoScope:
+          break;
+          // The binary parser validates the opcode for DebugInfoNone,
+          // DebugNoScope, DebugOperation. We just check the parameters to
+          // DebugOperation are properly constants for vulkan debug info.
+        case CommonDebugInfoDebugOperation: {
+          CHECK_CONST_UINT_OPERAND("Operation", 5);
+          for (uint32_t i = 6; i < num_words; ++i) {
+            CHECK_CONST_UINT_OPERAND("Operand", i);
+          }
+          break;
         }
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeArray: {
-        auto validate_base_type = ValidateOperandDebugType(
-            _, "Base Type", inst, 5, ext_inst_name, false);
-        if (validate_base_type != SPV_SUCCESS) return validate_base_type;
-        for (uint32_t i = 6; i < num_words; ++i) {
-          bool invalid = false;
-          auto* component_count = _.FindDef(inst->word(i));
-          if (IsConstIntScalarTypeWith32Or64Bits(_, component_count)) {
-            // TODO: We need a spec discussion for the bindless array.
-            if (!component_count->word(3)) {
-              invalid = true;
+        case CommonDebugInfoDebugCompilationUnit: {
+          CHECK_CONST_UINT_OPERAND("Version", 5);
+          CHECK_CONST_UINT_OPERAND("DWARF Version", 6);
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Language", 8);
+          break;
+        }
+        case CommonDebugInfoDebugSource: {
+          CHECK_OPERAND("File", SpvOpString, 5);
+          if (num_words == 7) CHECK_OPERAND("Text", SpvOpString, 6);
+          break;
+        }
+        case CommonDebugInfoDebugTypeBasic: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          CHECK_OPERAND("Size", SpvOpConstant, 6);
+          CHECK_CONST_UINT_OPERAND("Encoding", 7);
+          break;
+        }
+        case CommonDebugInfoDebugTypePointer: {
+          auto validate_base_type =
+              ValidateOperandBaseType(_, inst, 5, ext_inst_name);
+          if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+          CHECK_CONST_UINT_OPERAND("Storage Class", 6);
+          CHECK_CONST_UINT_OPERAND("Flags", 7);
+          break;
+        }
+        case CommonDebugInfoDebugTypeQualifier: {
+          auto validate_base_type =
+              ValidateOperandBaseType(_, inst, 5, ext_inst_name);
+          if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+          CHECK_CONST_UINT_OPERAND("Type Qualifier", 6);
+          break;
+        }
+        case CommonDebugInfoDebugTypeVector: {
+          auto validate_base_type =
+              ValidateOperandBaseType(_, inst, 5, ext_inst_name);
+          if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+
+          CHECK_CONST_UINT_OPERAND("Component Count", 6);
+          uint32_t component_count = inst->word(6);
+          if (vulkanDebugInfo) {
+            uint64_t const_val;
+            if (!_.GetConstantValUint64(component_count, &const_val)) {
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << ext_inst_name()
+                     << ": Component Count must be 32-bit integer OpConstant";
             }
-          } else if (component_count->words().size() > 6 &&
-                     (OpenCLDebugInfo100Instructions(component_count->word(
-                          4)) == OpenCLDebugInfo100DebugLocalVariable ||
-                      OpenCLDebugInfo100Instructions(component_count->word(
-                          4)) == OpenCLDebugInfo100DebugGlobalVariable)) {
-            auto* component_count_type = _.FindDef(component_count->word(6));
-            if (component_count_type->words().size() > 7) {
-              if (OpenCLDebugInfo100Instructions(component_count_type->word(
-                      4)) != OpenCLDebugInfo100DebugTypeBasic ||
-                  OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(
-                      component_count_type->word(7)) !=
-                      OpenCLDebugInfo100Unsigned) {
+            component_count = const_val & 0xffffffff;
+          }
+
+          if (!component_count || component_count > 4) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << ext_inst_name() << ": Component Count must be positive "
+                   << "integer less than or equal to 4";
+          }
+          break;
+        }
+        case CommonDebugInfoDebugTypeArray: {
+          auto validate_base_type = ValidateOperandDebugType(
+              _, "Base Type", inst, 5, ext_inst_name, false);
+          if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+          for (uint32_t i = 6; i < num_words; ++i) {
+            bool invalid = false;
+            auto* component_count = _.FindDef(inst->word(i));
+            if (IsConstIntScalarTypeWith32Or64Bits(_, component_count)) {
+              // TODO: We need a spec discussion for the bindless array.
+              if (!component_count->word(3)) {
                 invalid = true;
-              } else {
-                // DebugTypeBasic for DebugLocalVariable/DebugGlobalVariable
-                // must have Unsigned encoding and 32 or 64 as its size in bits.
-                Instruction* size_in_bits =
-                    _.FindDef(component_count_type->word(6));
-                if (!_.IsIntScalarType(size_in_bits->type_id()) ||
-                    (size_in_bits->word(3) != 32 &&
-                     size_in_bits->word(3) != 64)) {
+              }
+            } else if (component_count->words().size() > 6 &&
+                       (CommonDebugInfoInstructions(component_count->word(4)) ==
+                            CommonDebugInfoDebugLocalVariable ||
+                        CommonDebugInfoInstructions(component_count->word(4)) ==
+                            CommonDebugInfoDebugGlobalVariable)) {
+              auto* component_count_type = _.FindDef(component_count->word(6));
+              if (component_count_type->words().size() > 7) {
+                uint32_t encoding = component_count_type->word(7);
+                if (CommonDebugInfoInstructions(component_count_type->word(
+                        4)) != CommonDebugInfoDebugTypeBasic ||
+                    (vulkanDebugInfo && !IsUint32Constant(_, encoding)) ||
+                    OpenCLDebugInfo100DebugBaseTypeAttributeEncoding(
+                        vulkanDebugInfo
+                            ? GetUint32Constant(_, encoding)
+                            : encoding) != OpenCLDebugInfo100Unsigned) {
                   invalid = true;
+                } else {
+                  // DebugTypeBasic for DebugLocalVariable/DebugGlobalVariable
+                  // must have Unsigned encoding and 32 or 64 as its size in
+                  // bits.
+                  Instruction* size_in_bits =
+                      _.FindDef(component_count_type->word(6));
+                  if (!_.IsIntScalarType(size_in_bits->type_id()) ||
+                      (size_in_bits->word(3) != 32 &&
+                       size_in_bits->word(3) != 64)) {
+                    invalid = true;
+                  }
                 }
+              } else {
+                invalid = true;
               }
             } else {
               invalid = true;
             }
+            if (invalid) {
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << ext_inst_name() << ": Component Count must be "
+                     << "OpConstant with a 32- or 64-bits integer scalar type "
+                        "or "
+                     << "DebugGlobalVariable or DebugLocalVariable with a 32- "
+                        "or "
+                     << "64-bits unsigned integer scalar type";
+            }
+          }
+          break;
+        }
+        case CommonDebugInfoDebugTypedef: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          auto validate_base_type =
+              ValidateOperandBaseType(_, inst, 6, ext_inst_name);
+          if (validate_base_type != SPV_SUCCESS) return validate_base_type;
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          auto validate_parent =
+              ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+          if (validate_parent != SPV_SUCCESS) return validate_parent;
+          break;
+        }
+        case CommonDebugInfoDebugTypeFunction: {
+          CHECK_CONST_UINT_OPERAND("Flags", 5);
+          auto* return_type = _.FindDef(inst->word(6));
+          // TODO: We need a spec discussion that we have to allow return and
+          // parameter types of a DebugTypeFunction to have template parameter.
+          if (return_type->opcode() != SpvOpTypeVoid) {
+            auto validate_return = ValidateOperandDebugType(
+                _, "Return Type", inst, 6, ext_inst_name, true);
+            if (validate_return != SPV_SUCCESS) return validate_return;
+          }
+          for (uint32_t word_index = 7; word_index < num_words; ++word_index) {
+            auto validate_param = ValidateOperandDebugType(
+                _, "Parameter Types", inst, word_index, ext_inst_name, true);
+            if (validate_param != SPV_SUCCESS) return validate_param;
+          }
+          break;
+        }
+        case CommonDebugInfoDebugTypeEnum: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          if (!DoesDebugInfoOperandMatchExpectation(
+                  _,
+                  [](CommonDebugInfoInstructions dbg_inst) {
+                    return dbg_inst == CommonDebugInfoDebugInfoNone;
+                  },
+                  inst, 6)) {
+            auto validate_underlying_type = ValidateOperandDebugType(
+                _, "Underlying Types", inst, 6, ext_inst_name, false);
+            if (validate_underlying_type != SPV_SUCCESS)
+              return validate_underlying_type;
+          }
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          auto validate_parent =
+              ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+          if (validate_parent != SPV_SUCCESS) return validate_parent;
+          CHECK_OPERAND("Size", SpvOpConstant, 11);
+          auto* size = _.FindDef(inst->word(11));
+          if (!_.IsIntScalarType(size->type_id()) || !size->word(3)) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << ext_inst_name() << ": expected operand Size is a "
+                   << "positive integer";
+          }
+          CHECK_CONST_UINT_OPERAND("Flags", 12);
+          for (uint32_t word_index = 13; word_index + 1 < num_words;
+               word_index += 2) {
+            CHECK_OPERAND("Value", SpvOpConstant, word_index);
+            CHECK_OPERAND("Name", SpvOpString, word_index + 1);
+          }
+          break;
+        }
+        case CommonDebugInfoDebugTypeComposite: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          auto validate_parent =
+              ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+          if (validate_parent != SPV_SUCCESS) return validate_parent;
+          CHECK_OPERAND("Linkage Name", SpvOpString, 11);
+          if (!DoesDebugInfoOperandMatchExpectation(
+                  _,
+                  [](CommonDebugInfoInstructions dbg_inst) {
+                    return dbg_inst == CommonDebugInfoDebugInfoNone;
+                  },
+                  inst, 12)) {
+            CHECK_OPERAND("Size", SpvOpConstant, 12);
+          }
+          CHECK_CONST_UINT_OPERAND("Flags", 13);
+          for (uint32_t word_index = 14; word_index < num_words; ++word_index) {
+            if (!DoesDebugInfoOperandMatchExpectation(
+                    _,
+                    [](CommonDebugInfoInstructions dbg_inst) {
+                      return dbg_inst == CommonDebugInfoDebugTypeMember ||
+                             dbg_inst == CommonDebugInfoDebugFunction ||
+                             dbg_inst == CommonDebugInfoDebugTypeInheritance;
+                    },
+                    inst, word_index)) {
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << ext_inst_name() << ": "
+                     << "expected operand Members "
+                     << "must be DebugTypeMember, DebugFunction, or "
+                        "DebugTypeInheritance";
+            }
+          }
+          break;
+        }
+        case CommonDebugInfoDebugTypeMember: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          // TODO: We need a spec discussion that we have to allow member types
+          // to have template parameter.
+          auto validate_type =
+              ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true);
+          if (validate_type != SPV_SUCCESS) return validate_type;
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          // NonSemantic.Shader.DebugInfo doesn't have the Parent operand
+          if (vulkanDebugInfo) {
+            CHECK_OPERAND("Offset", SpvOpConstant, 10);
+            CHECK_OPERAND("Size", SpvOpConstant, 11);
+            CHECK_CONST_UINT_OPERAND("Flags", 12);
+            if (num_words == 14) CHECK_OPERAND("Value", SpvOpConstant, 13);
           } else {
-            invalid = true;
+            CHECK_DEBUG_OPERAND("Parent", CommonDebugInfoDebugTypeComposite,
+                                10);
+            CHECK_OPERAND("Offset", SpvOpConstant, 11);
+            CHECK_OPERAND("Size", SpvOpConstant, 12);
+            CHECK_CONST_UINT_OPERAND("Flags", 13);
+            if (num_words == 15) CHECK_OPERAND("Value", SpvOpConstant, 14);
           }
-          if (invalid) {
-            return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                   << ext_inst_name() << ": Component Count must be "
-                   << "OpConstant with a 32- or 64-bits integer scalar type or "
-                   << "DebugGlobalVariable or DebugLocalVariable with a 32- or "
-                   << "64-bits unsigned integer scalar type";
-          }
+          break;
         }
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypedef: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        auto validate_base_type =
-            ValidateOperandBaseType(_, inst, 6, ext_inst_name);
-        if (validate_base_type != SPV_SUCCESS) return validate_base_type;
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        auto validate_parent =
-            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
-        if (validate_parent != SPV_SUCCESS) return validate_parent;
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeFunction: {
-        auto* return_type = _.FindDef(inst->word(6));
-        // TODO: We need a spec discussion that we have to allow return and
-        // parameter types of a DebugTypeFunction to have template parameter.
-        if (return_type->opcode() != SpvOpTypeVoid) {
-          auto validate_return = ValidateOperandDebugType(
-              _, "Return Type", inst, 6, ext_inst_name, true);
-          if (validate_return != SPV_SUCCESS) return validate_return;
-        }
-        for (uint32_t word_index = 7; word_index < num_words; ++word_index) {
-          auto validate_param = ValidateOperandDebugType(
-              _, "Parameter Types", inst, word_index, ext_inst_name, true);
-          if (validate_param != SPV_SUCCESS) return validate_param;
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeEnum: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        if (!DoesDebugInfoOperandMatchExpectation(
-                _,
-                [](OpenCLDebugInfo100Instructions dbg_inst) {
-                  return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
-                },
-                inst, 6)) {
-          auto validate_underlying_type = ValidateOperandDebugType(
-              _, "Underlying Types", inst, 6, ext_inst_name, false);
-          if (validate_underlying_type != SPV_SUCCESS)
-            return validate_underlying_type;
-        }
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        auto validate_parent =
-            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
-        if (validate_parent != SPV_SUCCESS) return validate_parent;
-        CHECK_OPERAND("Size", SpvOpConstant, 11);
-        auto* size = _.FindDef(inst->word(11));
-        if (!_.IsIntScalarType(size->type_id()) || !size->word(3)) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << ext_inst_name() << ": expected operand Size is a "
-                 << "positive integer";
-        }
-        for (uint32_t word_index = 13; word_index + 1 < num_words;
-             word_index += 2) {
-          CHECK_OPERAND("Value", SpvOpConstant, word_index);
-          CHECK_OPERAND("Name", SpvOpString, word_index + 1);
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeComposite: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        auto validate_parent =
-            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
-        if (validate_parent != SPV_SUCCESS) return validate_parent;
-        CHECK_OPERAND("Linkage Name", SpvOpString, 11);
-        if (!DoesDebugInfoOperandMatchExpectation(
-                _,
-                [](OpenCLDebugInfo100Instructions dbg_inst) {
-                  return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
-                },
-                inst, 12)) {
-          CHECK_OPERAND("Size", SpvOpConstant, 12);
-        }
-        for (uint32_t word_index = 14; word_index < num_words; ++word_index) {
-          if (!DoesDebugInfoOperandMatchExpectation(
-                  _,
-                  [](OpenCLDebugInfo100Instructions dbg_inst) {
-                    return dbg_inst == OpenCLDebugInfo100DebugTypeMember ||
-                           dbg_inst == OpenCLDebugInfo100DebugFunction ||
-                           dbg_inst == OpenCLDebugInfo100DebugTypeInheritance;
-                  },
-                  inst, word_index)) {
+        case CommonDebugInfoDebugTypeInheritance: {
+          CHECK_DEBUG_OPERAND("Child", CommonDebugInfoDebugTypeComposite, 5);
+          auto* debug_inst = _.FindDef(inst->word(5));
+          auto composite_type =
+              OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6));
+          if (composite_type != OpenCLDebugInfo100Class &&
+              composite_type != OpenCLDebugInfo100Structure) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
                    << ext_inst_name() << ": "
-                   << "expected operand Members "
-                   << "must be DebugTypeMember, DebugFunction, or "
-                      "DebugTypeInheritance";
+                   << "expected operand Child must be class or struct debug "
+                      "type";
           }
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeMember: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        // TODO: We need a spec discussion that we have to allow member types
-        // to have template parameter.
-        auto validate_type =
-            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true);
-        if (validate_type != SPV_SUCCESS) return validate_type;
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 10);
-        CHECK_OPERAND("Offset", SpvOpConstant, 11);
-        CHECK_OPERAND("Size", SpvOpConstant, 12);
-        if (num_words == 15) CHECK_OPERAND("Value", SpvOpConstant, 14);
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeInheritance: {
-        CHECK_DEBUG_OPERAND("Child", OpenCLDebugInfo100DebugTypeComposite, 5);
-        auto* debug_inst = _.FindDef(inst->word(5));
-        auto composite_type =
-            OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6));
-        if (composite_type != OpenCLDebugInfo100Class &&
-            composite_type != OpenCLDebugInfo100Structure) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << ext_inst_name() << ": "
-                 << "expected operand Child must be class or struct debug type";
-        }
-        CHECK_DEBUG_OPERAND("Parent", OpenCLDebugInfo100DebugTypeComposite, 6);
-        debug_inst = _.FindDef(inst->word(6));
-        composite_type =
-            OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6));
-        if (composite_type != OpenCLDebugInfo100Class &&
-            composite_type != OpenCLDebugInfo100Structure) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << ext_inst_name() << ": "
-                 << "expected operand Parent must be class or struct debug "
-                    "type";
-        }
-        CHECK_OPERAND("Offset", SpvOpConstant, 7);
-        CHECK_OPERAND("Size", SpvOpConstant, 8);
-        break;
-      }
-      case OpenCLDebugInfo100DebugFunction: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        auto validate_type =
-            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false);
-        if (validate_type != SPV_SUCCESS) return validate_type;
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        auto validate_parent =
-            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
-        if (validate_parent != SPV_SUCCESS) return validate_parent;
-        CHECK_OPERAND("Linkage Name", SpvOpString, 11);
-        if (!DoesDebugInfoOperandMatchExpectation(
-                _,
-                [](OpenCLDebugInfo100Instructions dbg_inst) {
-                  return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
-                },
-                inst, 14)) {
-          CHECK_OPERAND("Function", SpvOpFunction, 14);
-        }
-        if (num_words == 16) {
-          CHECK_DEBUG_OPERAND("Declaration",
-                              OpenCLDebugInfo100DebugFunctionDeclaration, 15);
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugFunctionDeclaration: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        auto validate_type =
-            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false);
-        if (validate_type != SPV_SUCCESS) return validate_type;
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        auto validate_parent =
-            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
-        if (validate_parent != SPV_SUCCESS) return validate_parent;
-        CHECK_OPERAND("Linkage Name", SpvOpString, 11);
-        break;
-      }
-      case OpenCLDebugInfo100DebugLexicalBlock: {
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 5);
-        auto validate_parent =
-            ValidateOperandLexicalScope(_, "Parent", inst, 8, ext_inst_name);
-        if (validate_parent != SPV_SUCCESS) return validate_parent;
-        if (num_words == 10) CHECK_OPERAND("Name", SpvOpString, 9);
-        break;
-      }
-      case OpenCLDebugInfo100DebugScope: {
-        auto validate_scope =
-            ValidateOperandLexicalScope(_, "Scope", inst, 5, ext_inst_name);
-        if (validate_scope != SPV_SUCCESS) return validate_scope;
-        if (num_words == 7) {
-          CHECK_DEBUG_OPERAND("Inlined At", OpenCLDebugInfo100DebugInlinedAt,
-                              6);
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugLocalVariable: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        // TODO: We need a spec discussion that we have to allow local variable
-        // types to have template parameter.
-        auto validate_type =
-            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true);
-        if (validate_type != SPV_SUCCESS) return validate_type;
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        auto validate_parent =
-            ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
-        if (validate_parent != SPV_SUCCESS) return validate_parent;
-        break;
-      }
-      case OpenCLDebugInfo100DebugDeclare: {
-        CHECK_DEBUG_OPERAND("Local Variable",
-                            OpenCLDebugInfo100DebugLocalVariable, 5);
-        auto* operand = _.FindDef(inst->word(6));
-        if (operand->opcode() != SpvOpVariable &&
-            operand->opcode() != SpvOpFunctionParameter) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << ext_inst_name() << ": "
-                 << "expected operand Variable must be a result id of "
-                    "OpVariable or OpFunctionParameter";
-        }
-
-        CHECK_DEBUG_OPERAND("Expression", OpenCLDebugInfo100DebugExpression, 7);
-        break;
-      }
-      case OpenCLDebugInfo100DebugExpression: {
-        for (uint32_t word_index = 5; word_index < num_words; ++word_index) {
-          CHECK_DEBUG_OPERAND("Operation", OpenCLDebugInfo100DebugOperation,
-                              word_index);
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeTemplate: {
-        if (!DoesDebugInfoOperandMatchExpectation(
-                _,
-                [](OpenCLDebugInfo100Instructions dbg_inst) {
-                  return dbg_inst == OpenCLDebugInfo100DebugTypeComposite ||
-                         dbg_inst == OpenCLDebugInfo100DebugFunction;
-                },
-                inst, 5)) {
-          return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                 << ext_inst_name() << ": "
-                 << "expected operand Target must be DebugTypeComposite "
-                 << "or DebugFunction";
-        }
-        for (uint32_t word_index = 6; word_index < num_words; ++word_index) {
-          if (!DoesDebugInfoOperandMatchExpectation(
-                  _,
-                  [](OpenCLDebugInfo100Instructions dbg_inst) {
-                    return dbg_inst ==
-                               OpenCLDebugInfo100DebugTypeTemplateParameter ||
-                           dbg_inst ==
-                               OpenCLDebugInfo100DebugTypeTemplateTemplateParameter;
-                  },
-                  inst, word_index)) {
+          CHECK_DEBUG_OPERAND("Parent", CommonDebugInfoDebugTypeComposite, 6);
+          debug_inst = _.FindDef(inst->word(6));
+          composite_type =
+              OpenCLDebugInfo100DebugCompositeType(debug_inst->word(6));
+          if (composite_type != OpenCLDebugInfo100Class &&
+              composite_type != OpenCLDebugInfo100Structure) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
                    << ext_inst_name() << ": "
-                   << "expected operand Parameters must be "
-                   << "DebugTypeTemplateParameter or "
-                   << "DebugTypeTemplateTemplateParameter";
+                   << "expected operand Parent must be class or struct debug "
+                      "type";
           }
+          CHECK_OPERAND("Offset", SpvOpConstant, 7);
+          CHECK_OPERAND("Size", SpvOpConstant, 8);
+          CHECK_CONST_UINT_OPERAND("Flags", 9);
+          break;
         }
-        break;
-      }
-      case OpenCLDebugInfo100DebugTypeTemplateParameter: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        auto validate_actual_type = ValidateOperandDebugType(
-            _, "Actual Type", inst, 6, ext_inst_name, false);
-        if (validate_actual_type != SPV_SUCCESS) return validate_actual_type;
-        if (!DoesDebugInfoOperandMatchExpectation(
-                _,
-                [](OpenCLDebugInfo100Instructions dbg_inst) {
-                  return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
-                },
-                inst, 7)) {
-          CHECK_OPERAND("Value", SpvOpConstant, 7);
+        case CommonDebugInfoDebugFunction: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          auto validate_type = ValidateOperandDebugType(_, "Type", inst, 6,
+                                                        ext_inst_name, false);
+          if (validate_type != SPV_SUCCESS) return validate_type;
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          auto validate_parent =
+              ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+          if (validate_parent != SPV_SUCCESS) return validate_parent;
+          CHECK_OPERAND("Linkage Name", SpvOpString, 11);
+          CHECK_CONST_UINT_OPERAND("Flags", 12);
+          CHECK_CONST_UINT_OPERAND("Scope Line", 13);
+          // NonSemantic.Shader.DebugInfo.100 doesn't include a reference to the
+          // OpFunction
+          if (vulkanDebugInfo) {
+            if (num_words == 15) {
+              CHECK_DEBUG_OPERAND("Declaration",
+                                  CommonDebugInfoDebugFunctionDeclaration, 14);
+            }
+          } else {
+            if (!DoesDebugInfoOperandMatchExpectation(
+                    _,
+                    [](CommonDebugInfoInstructions dbg_inst) {
+                      return dbg_inst == CommonDebugInfoDebugInfoNone;
+                    },
+                    inst, 14)) {
+              CHECK_OPERAND("Function", SpvOpFunction, 14);
+            }
+            if (num_words == 16) {
+              CHECK_DEBUG_OPERAND("Declaration",
+                                  CommonDebugInfoDebugFunctionDeclaration, 15);
+            }
+          }
+          break;
         }
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 8);
-        break;
-      }
-      case OpenCLDebugInfo100DebugGlobalVariable: {
-        CHECK_OPERAND("Name", SpvOpString, 5);
-        auto validate_type =
-            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, false);
-        if (validate_type != SPV_SUCCESS) return validate_type;
-        CHECK_DEBUG_OPERAND("Source", OpenCLDebugInfo100DebugSource, 7);
-        auto validate_scope =
-            ValidateOperandLexicalScope(_, "Scope", inst, 10, ext_inst_name);
-        if (validate_scope != SPV_SUCCESS) return validate_scope;
-        CHECK_OPERAND("Linkage Name", SpvOpString, 11);
-        if (!DoesDebugInfoOperandMatchExpectation(
-                _,
-                [](OpenCLDebugInfo100Instructions dbg_inst) {
-                  return dbg_inst == OpenCLDebugInfo100DebugInfoNone;
-                },
-                inst, 12)) {
-          auto* operand = _.FindDef(inst->word(12));
+        case CommonDebugInfoDebugFunctionDeclaration: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          auto validate_type = ValidateOperandDebugType(_, "Type", inst, 6,
+                                                        ext_inst_name, false);
+          if (validate_type != SPV_SUCCESS) return validate_type;
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          auto validate_parent =
+              ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+          if (validate_parent != SPV_SUCCESS) return validate_parent;
+          CHECK_OPERAND("Linkage Name", SpvOpString, 11);
+          CHECK_CONST_UINT_OPERAND("Flags", 12);
+          break;
+        }
+        case CommonDebugInfoDebugLexicalBlock: {
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 5);
+          CHECK_CONST_UINT_OPERAND("Line", 6);
+          CHECK_CONST_UINT_OPERAND("Column", 7);
+          auto validate_parent =
+              ValidateOperandLexicalScope(_, "Parent", inst, 8, ext_inst_name);
+          if (validate_parent != SPV_SUCCESS) return validate_parent;
+          if (num_words == 10) CHECK_OPERAND("Name", SpvOpString, 9);
+          break;
+        }
+        case CommonDebugInfoDebugScope: {
+          auto validate_scope =
+              ValidateOperandLexicalScope(_, "Scope", inst, 5, ext_inst_name);
+          if (validate_scope != SPV_SUCCESS) return validate_scope;
+          if (num_words == 7) {
+            CHECK_DEBUG_OPERAND("Inlined At", CommonDebugInfoDebugInlinedAt, 6);
+          }
+          break;
+        }
+        case CommonDebugInfoDebugLocalVariable: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          // TODO: We need a spec discussion that we have to allow local
+          // variable types to have template parameter.
+          auto validate_type =
+              ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name, true);
+          if (validate_type != SPV_SUCCESS) return validate_type;
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          auto validate_parent =
+              ValidateOperandLexicalScope(_, "Parent", inst, 10, ext_inst_name);
+          if (validate_parent != SPV_SUCCESS) return validate_parent;
+          CHECK_CONST_UINT_OPERAND("Flags", 11);
+          if (num_words == 13) {
+            CHECK_CONST_UINT_OPERAND("ArgNumber", 12);
+          }
+          break;
+        }
+        case CommonDebugInfoDebugDeclare: {
+          CHECK_DEBUG_OPERAND("Local Variable",
+                              CommonDebugInfoDebugLocalVariable, 5);
+          auto* operand = _.FindDef(inst->word(6));
           if (operand->opcode() != SpvOpVariable &&
-              operand->opcode() != SpvOpConstant) {
+              operand->opcode() != SpvOpFunctionParameter) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
                    << ext_inst_name() << ": "
                    << "expected operand Variable must be a result id of "
-                      "OpVariable or OpConstant or DebugInfoNone";
+                      "OpVariable or OpFunctionParameter";
           }
-        }
-        if (num_words == 15) {
-          CHECK_DEBUG_OPERAND("Static Member Declaration",
-                              OpenCLDebugInfo100DebugTypeMember, 14);
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugInlinedAt: {
-        auto validate_scope =
-            ValidateOperandLexicalScope(_, "Scope", inst, 6, ext_inst_name);
-        if (validate_scope != SPV_SUCCESS) return validate_scope;
-        if (num_words == 8) {
-          CHECK_DEBUG_OPERAND("Inlined", OpenCLDebugInfo100DebugInlinedAt, 7);
-        }
-        break;
-      }
-      case OpenCLDebugInfo100DebugValue: {
-        CHECK_DEBUG_OPERAND("Local Variable",
-                            OpenCLDebugInfo100DebugLocalVariable, 5);
-        CHECK_DEBUG_OPERAND("Expression", OpenCLDebugInfo100DebugExpression, 7);
 
-        for (uint32_t word_index = 8; word_index < num_words; ++word_index) {
-          // TODO: The following code simply checks if it is a const int scalar
-          // or a DebugLocalVariable or DebugGlobalVariable, but we have to
-          // check it using the same validation for Indexes of OpAccessChain.
-          if (!IsConstWithIntScalarType(_, inst, word_index) &&
-              !IsDebugVariableWithIntScalarType(_, inst, word_index)) {
+          CHECK_DEBUG_OPERAND("Expression", CommonDebugInfoDebugExpression, 7);
+
+          if (vulkanDebugInfo) {
+            for (uint32_t word_index = 8; word_index < num_words;
+                 ++word_index) {
+              auto index_inst = _.FindDef(inst->word(word_index));
+              auto type_id = index_inst != nullptr ? index_inst->type_id() : 0;
+              if (type_id == 0 || !IsIntScalar(_, type_id, false, false))
+                return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                       << ext_inst_name() << ": "
+                       << "expected index must be scalar integer";
+            }
+          }
+          break;
+        }
+        case CommonDebugInfoDebugExpression: {
+          for (uint32_t word_index = 5; word_index < num_words; ++word_index) {
+            CHECK_DEBUG_OPERAND("Operation", CommonDebugInfoDebugOperation,
+                                word_index);
+          }
+          break;
+        }
+        case CommonDebugInfoDebugTypeTemplate: {
+          if (!DoesDebugInfoOperandMatchExpectation(
+                  _,
+                  [](CommonDebugInfoInstructions dbg_inst) {
+                    return dbg_inst == CommonDebugInfoDebugTypeComposite ||
+                           dbg_inst == CommonDebugInfoDebugFunction;
+                  },
+                  inst, 5)) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
-                   << ext_inst_name() << ": expected operand Indexes is "
-                   << "OpConstant, DebugGlobalVariable, or "
-                   << "type is OpConstant with an integer scalar type";
+                   << ext_inst_name() << ": "
+                   << "expected operand Target must be DebugTypeComposite "
+                   << "or DebugFunction";
           }
+          for (uint32_t word_index = 6; word_index < num_words; ++word_index) {
+            if (!DoesDebugInfoOperandMatchExpectation(
+                    _,
+                    [](CommonDebugInfoInstructions dbg_inst) {
+                      return dbg_inst ==
+                                 CommonDebugInfoDebugTypeTemplateParameter ||
+                             dbg_inst ==
+                                 CommonDebugInfoDebugTypeTemplateTemplateParameter;
+                    },
+                    inst, word_index)) {
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << ext_inst_name() << ": "
+                     << "expected operand Parameters must be "
+                     << "DebugTypeTemplateParameter or "
+                     << "DebugTypeTemplateTemplateParameter";
+            }
+          }
+          break;
         }
-        break;
-      }
+        case CommonDebugInfoDebugTypeTemplateParameter: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          auto validate_actual_type = ValidateOperandDebugType(
+              _, "Actual Type", inst, 6, ext_inst_name, false);
+          if (validate_actual_type != SPV_SUCCESS) return validate_actual_type;
+          if (!DoesDebugInfoOperandMatchExpectation(
+                  _,
+                  [](CommonDebugInfoInstructions dbg_inst) {
+                    return dbg_inst == CommonDebugInfoDebugInfoNone;
+                  },
+                  inst, 7)) {
+            CHECK_OPERAND("Value", SpvOpConstant, 7);
+          }
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 8);
+          CHECK_CONST_UINT_OPERAND("Line", 9);
+          CHECK_CONST_UINT_OPERAND("Column", 10);
+          break;
+        }
+        case CommonDebugInfoDebugGlobalVariable: {
+          CHECK_OPERAND("Name", SpvOpString, 5);
+          auto validate_type = ValidateOperandDebugType(_, "Type", inst, 6,
+                                                        ext_inst_name, false);
+          if (validate_type != SPV_SUCCESS) return validate_type;
+          CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7);
+          CHECK_CONST_UINT_OPERAND("Line", 8);
+          CHECK_CONST_UINT_OPERAND("Column", 9);
+          auto validate_scope =
+              ValidateOperandLexicalScope(_, "Scope", inst, 10, ext_inst_name);
+          if (validate_scope != SPV_SUCCESS) return validate_scope;
+          CHECK_OPERAND("Linkage Name", SpvOpString, 11);
+          if (!DoesDebugInfoOperandMatchExpectation(
+                  _,
+                  [](CommonDebugInfoInstructions dbg_inst) {
+                    return dbg_inst == CommonDebugInfoDebugInfoNone;
+                  },
+                  inst, 12)) {
+            auto* operand = _.FindDef(inst->word(12));
+            if (operand->opcode() != SpvOpVariable &&
+                operand->opcode() != SpvOpConstant) {
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << ext_inst_name() << ": "
+                     << "expected operand Variable must be a result id of "
+                        "OpVariable or OpConstant or DebugInfoNone";
+            }
+          }
+          if (num_words == 15) {
+            CHECK_DEBUG_OPERAND("Static Member Declaration",
+                                CommonDebugInfoDebugTypeMember, 14);
+          }
+          break;
+        }
+        case CommonDebugInfoDebugInlinedAt: {
+          CHECK_CONST_UINT_OPERAND("Line", 5);
+          auto validate_scope =
+              ValidateOperandLexicalScope(_, "Scope", inst, 6, ext_inst_name);
+          if (validate_scope != SPV_SUCCESS) return validate_scope;
+          if (num_words == 8) {
+            CHECK_DEBUG_OPERAND("Inlined", CommonDebugInfoDebugInlinedAt, 7);
+          }
+          break;
+        }
+        case CommonDebugInfoDebugValue: {
+          CHECK_DEBUG_OPERAND("Local Variable",
+                              CommonDebugInfoDebugLocalVariable, 5);
+          CHECK_DEBUG_OPERAND("Expression", CommonDebugInfoDebugExpression, 7);
 
-      // TODO: Add validation rules for remaining cases as well.
-      case OpenCLDebugInfo100DebugTypePtrToMember:
-      case OpenCLDebugInfo100DebugTypeTemplateTemplateParameter:
-      case OpenCLDebugInfo100DebugTypeTemplateParameterPack:
-      case OpenCLDebugInfo100DebugLexicalBlockDiscriminator:
-      case OpenCLDebugInfo100DebugInlinedVariable:
-      case OpenCLDebugInfo100DebugMacroDef:
-      case OpenCLDebugInfo100DebugMacroUndef:
-      case OpenCLDebugInfo100DebugImportedEntity:
-        break;
-      case OpenCLDebugInfo100InstructionsMax:
-        assert(0);
-        break;
+          for (uint32_t word_index = 8; word_index < num_words; ++word_index) {
+            // TODO: The following code simply checks if it is a const int
+            // scalar or a DebugLocalVariable or DebugGlobalVariable, but we
+            // have to check it using the same validation for Indexes of
+            // OpAccessChain.
+            if (!IsConstWithIntScalarType(_, inst, word_index) &&
+                !IsDebugVariableWithIntScalarType(_, inst, word_index)) {
+              return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                     << ext_inst_name() << ": expected operand Indexes is "
+                     << "OpConstant, DebugGlobalVariable, or "
+                     << "type is OpConstant with an integer scalar type";
+            }
+          }
+          break;
+        }
+
+        // TODO: Add validation rules for remaining cases as well.
+        case CommonDebugInfoDebugTypePtrToMember:
+        case CommonDebugInfoDebugTypeTemplateTemplateParameter:
+        case CommonDebugInfoDebugTypeTemplateParameterPack:
+        case CommonDebugInfoDebugLexicalBlockDiscriminator:
+        case CommonDebugInfoDebugInlinedVariable:
+        case CommonDebugInfoDebugMacroDef:
+        case CommonDebugInfoDebugMacroUndef:
+        case CommonDebugInfoDebugImportedEntity:
+          break;
+        case CommonDebugInfoInstructionsMax:
+          assert(0);
+          break;
+      }
     }
   } else if (ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) {
     auto import_inst = _.FindDef(inst->GetOperandAs<uint32_t>(2));
@@ -3124,6 +3293,7 @@
 
 spv_result_t ExtensionPass(ValidationState_t& _, const Instruction* inst) {
   const SpvOp opcode = inst->opcode();
+  if (opcode == SpvOpExtension) return ValidateExtension(_, inst);
   if (opcode == SpvOpExtInstImport) return ValidateExtInstImport(_, inst);
   if (opcode == SpvOpExtInst) return ValidateExtInst(_, inst);
 
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index fc37f08..64f6ba7 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -66,6 +66,11 @@
     case SpvImageOperandsVolatileTexelKHRMask:
     case SpvImageOperandsSignExtendMask:
     case SpvImageOperandsZeroExtendMask:
+    // TODO(jaebaek): Move this line properly after handling image offsets
+    //                operand. This line temporarily fixes CI failure that
+    //                blocks other PRs.
+    // https://github.com/KhronosGroup/SPIRV-Tools/issues/4565
+    case SpvImageOperandsOffsetsMask:
       return true;
   }
   return false;
@@ -281,12 +286,14 @@
   // the module to be invalid.
   if (mask == 0) return SPV_SUCCESS;
 
-  if (spvtools::utils::CountSetBits(
-          mask & (SpvImageOperandsOffsetMask | SpvImageOperandsConstOffsetMask |
-                  SpvImageOperandsConstOffsetsMask)) > 1) {
+  if (spvtools::utils::CountSetBits(mask & (SpvImageOperandsOffsetMask |
+                                            SpvImageOperandsConstOffsetMask |
+                                            SpvImageOperandsConstOffsetsMask |
+                                            SpvImageOperandsOffsetsMask)) > 1) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
-           << "Image Operands Offset, ConstOffset, ConstOffsets cannot be used "
-           << "together";
+           << _.VkErrorID(4662)
+           << "Image Operands Offset, ConstOffset, ConstOffsets, Offsets "
+              "cannot be used together";
   }
 
   const bool is_implicit_lod = IsImplicitLod(opcode);
@@ -440,6 +447,18 @@
              << "Expected Image Operand Offset to have " << plane_size
              << " components, but given " << offset_size;
     }
+
+    if (!_.options()->before_hlsl_legalization &&
+        spvIsVulkanEnv(_.context()->target_env)) {
+      if (opcode != SpvOpImageGather && opcode != SpvOpImageDrefGather &&
+          opcode != SpvOpImageSparseGather &&
+          opcode != SpvOpImageSparseDrefGather) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << _.VkErrorID(4663)
+               << "Image Operand Offset can only be used with "
+                  "OpImage*Gather operations";
+      }
+    }
   }
 
   if (mask & SpvImageOperandsConstOffsetsMask) {
@@ -607,6 +626,10 @@
     // setup.
   }
 
+  if (mask & SpvImageOperandsOffsetsMask) {
+    // TODO: add validation
+  }
+
   return SPV_SUCCESS;
 }
 
@@ -887,6 +910,7 @@
   // Vulkan uses the Sampled=1 case.
   if ((info.sampled != 0) && (info.sampled != 1)) {
     return _.diag(SPV_ERROR_INVALID_DATA, inst)
+           << _.VkErrorID(4657)
            << "Sampled image type requires an image type with \"Sampled\" "
               "operand set to 0 or 1";
   }
@@ -1444,12 +1468,21 @@
   }
 
   if (opcode == SpvOpImageGather || opcode == SpvOpImageSparseGather) {
-    const uint32_t component_index_type = _.GetOperandTypeId(inst, 4);
+    const uint32_t component = inst->GetOperandAs<uint32_t>(4);
+    const uint32_t component_index_type = _.GetTypeId(component);
     if (!_.IsIntScalarType(component_index_type) ||
         _.GetBitWidth(component_index_type) != 32) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Expected Component to be 32-bit int scalar";
     }
+    if (spvIsVulkanEnv(_.context()->target_env)) {
+      if (!spvOpcodeIsConstant(_.GetIdOpcode(component))) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << _.VkErrorID(4664)
+               << "Expected Component Operand to be a const object for Vulkan "
+                  "environment";
+      }
+    }
   } else {
     assert(opcode == SpvOpImageDrefGather ||
            opcode == SpvOpImageSparseDrefGather);
@@ -1487,8 +1520,8 @@
   if (spvIsVulkanEnv(target_env)) {
     if (_.GetDimension(actual_result_type) != 4) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "Expected " << GetActualResultTypeStr(opcode)
-             << " to have 4 components";
+             << _.VkErrorID(4780) << "Expected "
+             << GetActualResultTypeStr(opcode) << " to have 4 components";
     }
   }  // Check OpenCL below, after we get the image info.
 
@@ -2035,11 +2068,13 @@
                                       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 (models &&
+              models->find(SpvExecutionModelGLCompute) != models->end() &&
+              (!modes ||
+               (modes->find(SpvExecutionModeDerivativeGroupLinearNV) ==
+                    modes->end() &&
+                modes->find(SpvExecutionModeDerivativeGroupQuadsNV) ==
+                    modes->end()))) {
             if (message) {
               *message =
                   std::string(
diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp
index 9d395fb..dad9867 100644
--- a/source/val/validate_instruction.cpp
+++ b/source/val/validate_instruction.cpp
@@ -318,10 +318,9 @@
 
     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.";
+             << spvOpcodeString(opcode) << " requires SPIR-V version "
+             << SPV_SPIRV_VERSION_MAJOR_PART(min_version) << "."
+             << SPV_SPIRV_VERSION_MINOR_PART(min_version) << " at minimum.";
     }
   } else if (!_.HasAnyOfExtensions(exts)) {
     // Otherwise, we only error out when no enabling extensions are
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index d16d48e..7ccb637 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -27,6 +27,10 @@
 namespace val {
 namespace {
 
+// Limit the number of checked locations to 4096. Multiplied by 4 to represent
+// all the components. This limit is set to be well beyond practical use cases.
+const uint32_t kMaxLocations = 4096 * 4;
+
 // Returns true if \c inst is an input or output variable.
 bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
   if (is_spv_1_4) {
@@ -195,6 +199,10 @@
           NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
       num_components *= type->GetOperandAs<uint32_t>(2);
       break;
+    case SpvOpTypeArray:
+      // Skip the array.
+      return NumConsumedComponents(_,
+                                   _.FindDef(type->GetOperandAs<uint32_t>(1)));
     default:
       // This is an error that is validated elsewhere.
       break;
@@ -347,6 +355,11 @@
       uint32_t num_components = NumConsumedComponents(_, sub_type);
       uint32_t array_location = location + (num_locations * array_idx);
       uint32_t start = array_location * 4;
+      if (kMaxLocations <= start) {
+        // Too many locations, give up.
+        break;
+      }
+
       uint32_t end = (array_location + num_locations) * 4;
       if (num_components != 0) {
         start += component;
@@ -416,17 +429,41 @@
       }
 
       uint32_t start = location * 4;
-      uint32_t end = (location + num_locations) * 4;
-      if (num_components != 0) {
-        start += component;
-        end = location * 4 + component + num_components;
+      if (kMaxLocations <= start) {
+        // Too many locations, give up.
+        continue;
       }
-      for (uint32_t l = start; l < end; ++l) {
-        if (!locations->insert(l).second) {
-          return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
-                 << "Entry-point has conflicting " << storage_class
-                 << " location assignment at location " << l / 4
-                 << ", component " << l % 4;
+
+      if (member->opcode() == SpvOpTypeArray && num_components >= 1 &&
+          num_components < 4) {
+        // When an array has an element that takes less than a location in
+        // size, calculate the used locations in a strided manner.
+        for (uint32_t l = location; l < num_locations + location; ++l) {
+          for (uint32_t c = component; c < component + num_components; ++c) {
+            uint32_t check = 4 * l + c;
+            if (!locations->insert(check).second) {
+              return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
+                     << "Entry-point has conflicting " << storage_class
+                     << " location assignment at location " << l
+                     << ", component " << c;
+            }
+          }
+        }
+      } else {
+        // TODO: There is a hole here is the member is an array of 3- or
+        // 4-element vectors of 64-bit types.
+        uint32_t end = (location + num_locations) * 4;
+        if (num_components != 0) {
+          start += component;
+          end = location * 4 + component + num_components;
+        }
+        for (uint32_t l = start; l < end; ++l) {
+          if (!locations->insert(l).second) {
+            return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
+                   << "Entry-point has conflicting " << storage_class
+                   << " location assignment at location " << l / 4
+                   << ", component " << l % 4;
+          }
         }
       }
     }
@@ -454,6 +491,7 @@
   std::unordered_set<uint32_t> input_locations;
   std::unordered_set<uint32_t> output_locations_index0;
   std::unordered_set<uint32_t> output_locations_index1;
+  std::unordered_set<uint32_t> seen;
   for (uint32_t i = 3; i < entry_point->operands().size(); ++i) {
     auto interface_id = entry_point->GetOperandAs<uint32_t>(i);
     auto interface_var = _.FindDef(interface_id);
@@ -462,6 +500,11 @@
         storage_class != SpvStorageClassOutput) {
       continue;
     }
+    if (!seen.insert(interface_id).second) {
+      // Pre-1.4 an interface variable could be listed multiple times in an
+      // entry point. Validation for 1.4 or later is done elsewhere.
+      continue;
+    }
 
     auto locations = (storage_class == SpvStorageClassInput)
                          ? &input_locations
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp
index b53f991..d582321 100644
--- a/source/val/validate_layout.cpp
+++ b/source/val/validate_layout.cpp
@@ -17,6 +17,7 @@
 #include <cassert>
 
 #include "DebugInfo.h"
+#include "NonSemanticShaderDebugInfo100.h"
 #include "OpenCLDebugInfo100.h"
 #include "source/diagnostic.h"
 #include "source/opcode.h"
@@ -37,17 +38,7 @@
                                       const Instruction* inst, SpvOp opcode) {
   switch (opcode) {
     case SpvOpExtInst:
-      if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
-        // non-semantic extinst opcodes are allowed beginning in the types
-        // section, but since they must name a return type they cannot be the
-        // first instruction in the types section. Therefore check that we are
-        // already in it.
-        if (_.current_layout_section() < kLayoutTypes) {
-          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
-                 << "Non-semantic OpExtInst must not appear before types "
-                 << "section";
-        }
-      } else if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
+      if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
         const uint32_t ext_inst_index = inst->word(4);
         bool local_debug_info = false;
         if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
@@ -59,6 +50,20 @@
               ext_inst_key == OpenCLDebugInfo100DebugValue) {
             local_debug_info = true;
           }
+        } else if (inst->ext_inst_type() ==
+                   SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
+          const NonSemanticShaderDebugInfo100Instructions ext_inst_key =
+              NonSemanticShaderDebugInfo100Instructions(ext_inst_index);
+          if (ext_inst_key == NonSemanticShaderDebugInfo100DebugScope ||
+              ext_inst_key == NonSemanticShaderDebugInfo100DebugNoScope ||
+              ext_inst_key == NonSemanticShaderDebugInfo100DebugDeclare ||
+              ext_inst_key == NonSemanticShaderDebugInfo100DebugValue ||
+              ext_inst_key == NonSemanticShaderDebugInfo100DebugLine ||
+              ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine ||
+              ext_inst_key ==
+                  NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
+            local_debug_info = true;
+          }
         } else {
           const DebugInfoInstructions ext_inst_key =
               DebugInfoInstructions(ext_inst_index);
@@ -94,6 +99,16 @@
                    << "declarations)";
           }
         }
+      } else if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
+        // non-semantic extinst opcodes are allowed beginning in the types
+        // section, but since they must name a return type they cannot be the
+        // first instruction in the types section. Therefore check that we are
+        // already in it.
+        if (_.current_layout_section() < kLayoutTypes) {
+          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                 << "Non-semantic OpExtInst must not appear before types "
+                 << "section";
+        }
       } else {
         // otherwise they must be used in a block
         if (_.current_layout_section() < kLayoutFunctionDefinitions) {
@@ -230,20 +245,7 @@
         break;
 
       case SpvOpExtInst:
-        if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
-          // non-semantic extinst opcodes are allowed beginning in the types
-          // section, but must either be placed outside a function declaration,
-          // or inside a block.
-          if (_.current_layout_section() < kLayoutTypes) {
-            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
-                   << "Non-semantic OpExtInst must not appear before types "
-                   << "section";
-          } else if (_.in_function_body() && _.in_block() == false) {
-            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
-                   << "Non-semantic OpExtInst within function definition must "
-                      "appear in a block";
-          }
-        } else if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
+        if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
           const uint32_t ext_inst_index = inst->word(4);
           bool local_debug_info = false;
           if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
@@ -255,6 +257,20 @@
                 ext_inst_key == OpenCLDebugInfo100DebugValue) {
               local_debug_info = true;
             }
+          } else if (inst->ext_inst_type() ==
+                     SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
+            const NonSemanticShaderDebugInfo100Instructions ext_inst_key =
+                NonSemanticShaderDebugInfo100Instructions(ext_inst_index);
+            if (ext_inst_key == NonSemanticShaderDebugInfo100DebugScope ||
+                ext_inst_key == NonSemanticShaderDebugInfo100DebugNoScope ||
+                ext_inst_key == NonSemanticShaderDebugInfo100DebugDeclare ||
+                ext_inst_key == NonSemanticShaderDebugInfo100DebugValue ||
+                ext_inst_key == NonSemanticShaderDebugInfo100DebugLine ||
+                ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine ||
+                ext_inst_key ==
+                    NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
+              local_debug_info = true;
+            }
           } else {
             const DebugInfoInstructions ext_inst_key =
                 DebugInfoInstructions(ext_inst_index);
@@ -290,6 +306,19 @@
                      << "declarations)";
             }
           }
+        } else if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
+          // non-semantic extinst opcodes are allowed beginning in the types
+          // section, but must either be placed outside a function declaration,
+          // or inside a block.
+          if (_.current_layout_section() < kLayoutTypes) {
+            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                   << "Non-semantic OpExtInst must not appear before types "
+                   << "section";
+          } else if (_.in_function_body() && _.in_block() == false) {
+            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+                   << "Non-semantic OpExtInst within function definition must "
+                      "appear in a block";
+          }
         } else {
           // otherwise they must be used in a block
           if (_.in_block() == false) {
diff --git a/source/val/validate_logicals.cpp b/source/val/validate_logicals.cpp
index 5886dbf..bb35f55 100644
--- a/source/val/validate_logicals.cpp
+++ b/source/val/validate_logicals.cpp
@@ -188,7 +188,7 @@
           case SpvOpTypeStruct: {
             if (!composites) return fail();
             break;
-          };
+          }
 
           default:
             return fail();
diff --git a/source/val/validate_memory.cpp b/source/val/validate_memory.cpp
index 45a232d..a7b0f82 100644
--- a/source/val/validate_memory.cpp
+++ b/source/val/validate_memory.cpp
@@ -407,6 +407,10 @@
            << "' is not a pointer type.";
   }
 
+  const auto type_index = 2;
+  const auto value_id = result_type->GetOperandAs<uint32_t>(type_index);
+  auto value_type = _.FindDef(value_id);
+
   const auto initializer_index = 3;
   const auto storage_class_index = 2;
   if (initializer_index < inst->operands().size()) {
@@ -423,7 +427,7 @@
              << "OpVariable Initializer <id> '" << _.getIdName(initializer_id)
              << "' is not a constant or module-scope variable.";
     }
-    if (initializer->type_id() != result_type->GetOperandAs<uint32_t>(2u)) {
+    if (initializer->type_id() != value_id) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "Initializer type must match the type pointed to by the Result "
                 "Type";
@@ -440,9 +444,6 @@
       storage_class != SpvStorageClassHitAttributeNV &&
       storage_class != SpvStorageClassCallableDataNV &&
       storage_class != SpvStorageClassIncomingCallableDataNV) {
-    const auto storage_index = 2;
-    const auto storage_id = result_type->GetOperandAs<uint32_t>(storage_index);
-    const auto storage = _.FindDef(storage_id);
     bool storage_input_or_output = storage_class == SpvStorageClassInput ||
                                    storage_class == SpvStorageClassOutput;
     bool builtin = false;
@@ -455,7 +456,7 @@
       }
     }
     if (!(storage_input_or_output && builtin) &&
-        ContainsInvalidBool(_, storage, storage_input_or_output)) {
+        ContainsInvalidBool(_, value_type, storage_input_or_output)) {
       return _.diag(SPV_ERROR_INVALID_ID, inst)
              << "If OpTypeBool is stored in conjunction with OpVariable, it "
              << "can only be used with non-externally visible shader Storage "
@@ -465,6 +466,7 @@
 
   if (!_.IsValidStorageClass(storage_class)) {
     return _.diag(SPV_ERROR_INVALID_BINARY, inst)
+           << _.VkErrorID(4643)
            << "Invalid storage class for target environment";
   }
 
@@ -535,18 +537,14 @@
       if (!IsAllowedTypeOrArrayOfSame(
               _, pointee,
               {SpvOpTypeImage, SpvOpTypeSampler, SpvOpTypeSampledImage,
-               SpvOpTypeAccelerationStructureNV,
-               SpvOpTypeAccelerationStructureKHR, SpvOpTypeRayQueryKHR})) {
+               SpvOpTypeAccelerationStructureKHR})) {
         return _.diag(SPV_ERROR_INVALID_ID, inst)
-               << "UniformConstant OpVariable <id> '" << _.getIdName(inst->id())
-               << "' has illegal type.\n"
-               << "From Vulkan spec, section 14.5.2:\n"
+               << _.VkErrorID(4655) << "UniformConstant OpVariable <id> '"
+               << _.getIdName(inst->id()) << "' has illegal type.\n"
                << "Variables identified with the UniformConstant storage class "
                << "are used only as handles to refer to opaque resources. Such "
                << "variables must be typed as OpTypeImage, OpTypeSampler, "
-               << "OpTypeSampledImage, OpTypeAccelerationStructureNV, "
-                  "OpTypeAccelerationStructureKHR, "
-                  "OpTypeRayQueryKHR, "
+               << "OpTypeSampledImage, OpTypeAccelerationStructureKHR, "
                << "or an array of one of these types.";
       }
     }
@@ -576,6 +574,28 @@
                   "of this type";
       }
     }
+
+    // Check for invalid use of Invariant
+    if (storage_class != SpvStorageClassInput &&
+        storage_class != SpvStorageClassOutput) {
+      if (_.HasDecoration(inst->id(), SpvDecorationInvariant)) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << _.VkErrorID(4677)
+               << "Variable decorated with Invariant must only be identified "
+                  "with the Input or Output storage class in Vulkan "
+                  "environment.";
+      }
+      // Need to check if only the members in a struct are decorated
+      if (value_type && value_type->opcode() == SpvOpTypeStruct) {
+        if (_.HasDecoration(value_id, SpvDecorationInvariant)) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << _.VkErrorID(4677)
+                 << "Variable struct member decorated with Invariant must only "
+                    "be identified with the Input or Output storage class in "
+                    "Vulkan environment.";
+        }
+      }
+    }
   }
 
   // Vulkan Appendix A: Check that if contains initializer, then
@@ -584,16 +604,26 @@
       storage_class != SpvStorageClassPrivate &&
       storage_class != SpvStorageClassFunction) {
     if (spvIsVulkanEnv(_.context()->target_env)) {
-      return _.diag(SPV_ERROR_INVALID_ID, inst)
-             << _.VkErrorID(4651) << "OpVariable, <id> '"
-             << _.getIdName(inst->id())
-             << "', has a disallowed initializer & storage class "
-             << "combination.\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";
+      if (storage_class == SpvStorageClassWorkgroup) {
+        auto init_id = inst->GetOperandAs<uint32_t>(3);
+        auto init = _.FindDef(init_id);
+        if (init->opcode() != SpvOpConstantNull) {
+          return _.diag(SPV_ERROR_INVALID_ID, inst)
+                 << "Variable initializers in Workgroup storage class are "
+                    "limited to OpConstantNull";
+        }
+      } else {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << _.VkErrorID(4651) << "OpVariable, <id> '"
+               << _.getIdName(inst->id())
+               << "', has a disallowed initializer & storage class "
+               << "combination.\n"
+               << "From " << spvLogStringForEnv(_.context()->target_env)
+               << " spec:\n"
+               << "Variable declarations that include initializers must have "
+               << "one of the following storage classes: Output, Private, "
+               << "Function or Workgroup";
+      }
     }
   }
 
@@ -630,9 +660,6 @@
   }
 
   // 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)) {
     // OpTypeRuntimeArray should only ever be in a container like OpTypeStruct,
     // so should never appear as a bare variable.
@@ -749,6 +776,11 @@
             storage_class_ok = false;
           }
           break;
+        case SpvStorageClassWorkgroup:
+          if (!_.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayout16BitAccessKHR)) {
+            storage_class_ok = false;
+          }
+          break;
         default:
           return _.diag(SPV_ERROR_INVALID_ID, inst)
                  << "Cannot allocate a variable containing a 16-bit type in "
@@ -800,6 +832,11 @@
             storage_class_ok = false;
           }
           break;
+        case SpvStorageClassWorkgroup:
+          if (!_.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayout8BitAccessKHR)) {
+            storage_class_ok = false;
+          }
+          break;
         default:
           return _.diag(SPV_ERROR_INVALID_ID, inst)
                  << "Cannot allocate a variable containing a 8-bit type in "
@@ -856,6 +893,12 @@
            << "'s type.";
   }
 
+  if (!_.options()->before_hlsl_legalization &&
+      _.ContainsRuntimeArray(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Cannot load a runtime-sized array";
+  }
+
   if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
 
   if (_.HasCapability(SpvCapabilityShader) &&
diff --git a/source/val/validate_memory_semantics.cpp b/source/val/validate_memory_semantics.cpp
index 8e47f8a..d918931 100644
--- a/source/val/validate_memory_semantics.cpp
+++ b/source/val/validate_memory_semantics.cpp
@@ -25,7 +25,8 @@
 
 spv_result_t ValidateMemorySemantics(ValidationState_t& _,
                                      const Instruction* inst,
-                                     uint32_t operand_index) {
+                                     uint32_t operand_index,
+                                     uint32_t memory_scope) {
   const SpvOp opcode = inst->opcode();
   const auto id = inst->GetOperandAs<const uint32_t>(operand_index);
   bool is_int32 = false, is_const_int32 = false;
@@ -172,17 +173,29 @@
 
     if (opcode == SpvOpMemoryBarrier && !num_memory_order_set_bits) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << _.VkErrorID(4649) << spvOpcodeString(opcode)
+             << _.VkErrorID(4732) << spvOpcodeString(opcode)
              << ": Vulkan specification requires Memory Semantics to have "
                 "one "
                 "of the following bits set: Acquire, Release, "
                 "AcquireRelease "
                 "or SequentiallyConsistent";
+    } else if (opcode != SpvOpMemoryBarrier && num_memory_order_set_bits) {
+      // should leave only atomics and control barriers for Vulkan env
+      bool memory_is_int32 = false, memory_is_const_int32 = false;
+      uint32_t memory_value = 0;
+      std::tie(memory_is_int32, memory_is_const_int32, memory_value) =
+          _.EvalInt32IfConst(memory_scope);
+      if (memory_is_int32 && memory_value == SpvScopeInvocation) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << _.VkErrorID(4641) << spvOpcodeString(opcode)
+               << ": Vulkan specification requires Memory Semantics to be None "
+                  "if used with Invocation Memory Scope";
+      }
     }
 
     if (opcode == SpvOpMemoryBarrier && !includes_storage_class) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << _.VkErrorID(4649) << spvOpcodeString(opcode)
+             << _.VkErrorID(4733) << spvOpcodeString(opcode)
              << ": expected Memory Semantics to include a Vulkan-supported "
                 "storage class";
     }
@@ -223,6 +236,7 @@
          value & SpvMemorySemanticsAcquireReleaseMask ||
          value & SpvMemorySemanticsSequentiallyConsistentMask)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << _.VkErrorID(4731)
              << "Vulkan spec disallows OpAtomicLoad with Memory Semantics "
                 "Release, AcquireRelease and SequentiallyConsistent";
     }
@@ -232,6 +246,7 @@
          value & SpvMemorySemanticsAcquireReleaseMask ||
          value & SpvMemorySemanticsSequentiallyConsistentMask)) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << _.VkErrorID(4730)
              << "Vulkan spec disallows OpAtomicStore with Memory Semantics "
                 "Acquire, AcquireRelease and SequentiallyConsistent";
     }
diff --git a/source/val/validate_memory_semantics.h b/source/val/validate_memory_semantics.h
index 72a3e10..9e6f93a 100644
--- a/source/val/validate_memory_semantics.h
+++ b/source/val/validate_memory_semantics.h
@@ -22,7 +22,8 @@
 
 spv_result_t ValidateMemorySemantics(ValidationState_t& _,
                                      const Instruction* inst,
-                                     uint32_t operand_index);
+                                     uint32_t operand_index,
+                                     uint32_t memory_scope);
 
 }  // namespace val
 }  // namespace spvtools
diff --git a/source/val/validate_misc.cpp b/source/val/validate_misc.cpp
index 0c30f3c..3bc15ca 100644
--- a/source/val/validate_misc.cpp
+++ b/source/val/validate_misc.cpp
@@ -72,6 +72,37 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t ValidateAssumeTrue(ValidationState_t& _, const Instruction* inst) {
+  const auto operand_type_id = _.GetOperandTypeId(inst, 0);
+  if (!operand_type_id || !_.IsBoolScalarType(operand_type_id)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Value operand of OpAssumeTrueKHR must be a boolean scalar";
+  }
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateExpect(ValidationState_t& _, const Instruction* inst) {
+  const auto result_type = inst->type_id();
+  if (!_.IsBoolScalarOrVectorType(result_type) &&
+      !_.IsIntScalarOrVectorType(result_type)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Result of OpExpectKHR must be a scalar or vector of integer "
+              "type or boolean type";
+  }
+
+  if (_.GetOperandTypeId(inst, 2) != result_type) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Type of Value operand of OpExpectKHR does not match the result "
+              "type ";
+  }
+  if (_.GetOperandTypeId(inst, 3) != result_type) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Type of ExpectedValue operand of OpExpectKHR does not match the "
+              "result type ";
+  }
+  return SPV_SUCCESS;
+}
+
 }  // namespace
 
 spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst) {
@@ -152,6 +183,16 @@
         return error;
       }
       break;
+    case SpvOpAssumeTrueKHR:
+      if (auto error = ValidateAssumeTrue(_, inst)) {
+        return error;
+      }
+      break;
+    case SpvOpExpectKHR:
+      if (auto error = ValidateExpect(_, inst)) {
+        return error;
+      }
+      break;
     default:
       break;
   }
diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp
index c816b75..9635268 100644
--- a/source/val/validate_mode_setting.cpp
+++ b/source/val/validate_mode_setting.cpp
@@ -225,13 +225,21 @@
                 }
               }
             }
+            if (i.opcode() == SpvOpExecutionModeId) {
+              const auto mode = i.GetOperandAs<SpvExecutionMode>(1);
+              if (mode == SpvExecutionModeLocalSizeId) {
+                ok = true;
+                break;
+              }
+            }
           }
           if (!ok) {
             return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << _.VkErrorID(6426)
                    << "In the Vulkan environment, GLCompute execution model "
-                      "entry points require either the LocalSize execution "
-                      "mode or an object decorated with WorkgroupSize must be "
-                      "specified.";
+                      "entry points require either the LocalSize or "
+                      "LocalSizeId execution mode or an object decorated with "
+                      "WorkgroupSize must be specified.";
           }
         }
         break;
@@ -428,6 +436,10 @@
       break;
     case SpvExecutionModeLocalSize:
     case SpvExecutionModeLocalSizeId:
+      if (mode == SpvExecutionModeLocalSizeId && !_.IsLocalSizeIdAllowed())
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "LocalSizeId mode is not allowed by the current environment.";
+
       if (!std::all_of(models->begin(), models->end(),
                        [&_](const SpvExecutionModel& model) {
                          switch (model) {
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index a92f7fd..29ba583 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -105,21 +105,30 @@
       }
     }
 
-    // If OpControlBarrier is used in fragment, vertex, tessellation evaluation,
-    // or geometry stages, the execution Scope must be Subgroup.
+    // OpControlBarrier must only use Subgroup execution scope for a subset of
+    // execution models.
     if (opcode == SpvOpControlBarrier && value != SpvScopeSubgroup) {
+      std::string errorVUID = _.VkErrorID(4682);
       _.function(inst->function()->id())
-          ->RegisterExecutionModelLimitation([](SpvExecutionModel model,
-                                                std::string* message) {
+          ->RegisterExecutionModelLimitation([errorVUID](
+                                                 SpvExecutionModel model,
+                                                 std::string* message) {
             if (model == SpvExecutionModelFragment ||
                 model == SpvExecutionModelVertex ||
                 model == SpvExecutionModelGeometry ||
-                model == SpvExecutionModelTessellationEvaluation) {
+                model == SpvExecutionModelTessellationEvaluation ||
+                model == SpvExecutionModelRayGenerationKHR ||
+                model == SpvExecutionModelIntersectionKHR ||
+                model == SpvExecutionModelAnyHitKHR ||
+                model == SpvExecutionModelClosestHitKHR ||
+                model == SpvExecutionModelMissKHR) {
               if (message) {
                 *message =
-                    "in Vulkan evironment, OpControlBarrier execution scope "
-                    "must be Subgroup for Fragment, Vertex, Geometry and "
-                    "TessellationEvaluation execution models";
+                    errorVUID +
+                    "in Vulkan environment, OpControlBarrier execution scope "
+                    "must be Subgroup for Fragment, Vertex, Geometry, "
+                    "TessellationEvaluation, RayGeneration, Intersection, "
+                    "AnyHit, ClosestHit, and Miss execution models";
               }
               return false;
             }
@@ -127,11 +136,34 @@
           });
     }
 
+    // Only subset of execution models support Workgroup.
+    if (value == SpvScopeWorkgroup) {
+      std::string errorVUID = _.VkErrorID(4637);
+      _.function(inst->function()->id())
+          ->RegisterExecutionModelLimitation(
+              [errorVUID](SpvExecutionModel model, std::string* message) {
+                if (model != SpvExecutionModelTaskNV &&
+                    model != SpvExecutionModelMeshNV &&
+                    model != SpvExecutionModelTessellationControl &&
+                    model != SpvExecutionModelGLCompute) {
+                  if (message) {
+                    *message =
+                        errorVUID +
+                        "in Vulkan environment, Workgroup execution scope is "
+                        "only for TaskNV, MeshNV, TessellationControl, and "
+                        "GLCompute execution models";
+                  }
+                  return false;
+                }
+                return true;
+              });
+    }
+
     // Vulkan generic rules
     // Scope for execution must be limited to Workgroup or Subgroup
     if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << spvOpcodeString(opcode)
+             << _.VkErrorID(4636) << spvOpcodeString(opcode)
              << ": in Vulkan environment Execution Scope is limited to "
              << "Workgroup and Subgroup";
     }
diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp
index 6a5ea3c..4376b52 100644
--- a/source/val/validate_type.cpp
+++ b/source/val/validate_type.cpp
@@ -427,7 +427,8 @@
   if (spvIsVulkanEnv(_.context()->target_env) &&
       !_.options()->before_hlsl_legalization && ContainsOpaqueType(_, inst)) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "In " << spvLogStringForEnv(_.context()->target_env)
+           << _.VkErrorID(4667) << "In "
+           << spvLogStringForEnv(_.context()->target_env)
            << ", OpTypeStruct must not contain an opaque type.";
   }
 
@@ -462,6 +463,7 @@
 
   if (!_.IsValidStorageClass(storage_class)) {
     return _.diag(SPV_ERROR_INVALID_BINARY, inst)
+           << _.VkErrorID(4643)
            << "Invalid storage class for target environment";
   }
 
@@ -594,7 +596,7 @@
   if (!cols || !_.IsIntScalarType(cols->type_id()) ||
       !spvOpcodeIsConstant(cols->opcode())) {
     return _.diag(SPV_ERROR_INVALID_ID, inst)
-           << "OpTypeCooperativeMatrixNV Cols <id> '" << _.getIdName(rows_id)
+           << "OpTypeCooperativeMatrixNV Cols <id> '" << _.getIdName(cols_id)
            << "' is not a constant instruction with scalar integer type.";
   }
 
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index b9269db..8d1a0d3 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -175,6 +175,9 @@
     }
   }
 
+  // LocalSizeId is always allowed in non-Vulkan environments.
+  features_.env_allow_localsizeid = !spvIsVulkanEnv(env);
+
   // Only attempt to count if we have words, otherwise let the other validation
   // fail and generate an error.
   if (num_words > 0) {
@@ -359,6 +362,7 @@
     case SpvCapabilityStorageBuffer8BitAccess:
     case SpvCapabilityUniformAndStorageBuffer8BitAccess:
     case SpvCapabilityStoragePushConstant8:
+    case SpvCapabilityWorkgroupMemoryExplicitLayout8BitAccessKHR:
       features_.declare_int8_type = true;
       break;
     case SpvCapabilityInt16:
@@ -372,6 +376,7 @@
     case SpvCapabilityStorageUniform16:
     case SpvCapabilityStoragePushConstant16:
     case SpvCapabilityStorageInputOutput16:
+    case SpvCapabilityWorkgroupMemoryExplicitLayout16BitAccessKHR:
       features_.declare_int16_type = true;
       features_.declare_float16_type = true;
       features_.free_fp_rounding_mode = true;
@@ -518,17 +523,39 @@
 void ValidationState_t::RegisterInstruction(Instruction* inst) {
   if (inst->id()) all_definitions_.insert(std::make_pair(inst->id(), inst));
 
-  // If the instruction is using an OpTypeSampledImage as an operand, it should
-  // be recorded. The validator will ensure that all usages of an
-  // OpTypeSampledImage and its definition are in the same basic block.
+  // Some validation checks are easier by getting all the consumers
   for (uint16_t i = 0; i < inst->operands().size(); ++i) {
     const spv_parsed_operand_t& operand = inst->operand(i);
-    if (SPV_OPERAND_TYPE_ID == operand.type) {
+    if ((SPV_OPERAND_TYPE_ID == operand.type) ||
+        (SPV_OPERAND_TYPE_TYPE_ID == operand.type)) {
       const uint32_t operand_word = inst->word(operand.offset);
       Instruction* operand_inst = FindDef(operand_word);
-      if (operand_inst && SpvOpSampledImage == operand_inst->opcode()) {
+      if (!operand_inst) {
+        continue;
+      }
+
+      // If the instruction is using an OpTypeSampledImage as an operand, it
+      // should be recorded. The validator will ensure that all usages of an
+      // OpTypeSampledImage and its definition are in the same basic block.
+      if ((SPV_OPERAND_TYPE_ID == operand.type) &&
+          (SpvOpSampledImage == operand_inst->opcode())) {
         RegisterSampledImageConsumer(operand_word, inst);
       }
+
+      // In order to track storage classes (not Function) used per execution
+      // model we can't use RegisterExecutionModelLimitation on instructions
+      // like OpTypePointer which are going to be in the pre-function section.
+      // Instead just need to register storage class usage for consumers in a
+      // function block.
+      if (inst->function()) {
+        if (operand_inst->opcode() == SpvOpTypePointer) {
+          RegisterStorageClassConsumer(
+              operand_inst->GetOperandAs<SpvStorageClass>(1), inst);
+        } else if (operand_inst->opcode() == SpvOpVariable) {
+          RegisterStorageClassConsumer(
+              operand_inst->GetOperandAs<SpvStorageClass>(2), inst);
+        }
+      }
     }
   }
 }
@@ -548,6 +575,57 @@
   sampled_image_consumers_[sampled_image_id].push_back(consumer);
 }
 
+void ValidationState_t::RegisterStorageClassConsumer(
+    SpvStorageClass storage_class, Instruction* consumer) {
+  if (spvIsVulkanEnv(context()->target_env)) {
+    if (storage_class == SpvStorageClassOutput) {
+      std::string errorVUID = VkErrorID(4644);
+      function(consumer->function()->id())
+          ->RegisterExecutionModelLimitation([errorVUID](
+              SpvExecutionModel model, std::string* message) {
+            if (model == SpvExecutionModelGLCompute ||
+                model == SpvExecutionModelRayGenerationKHR ||
+                model == SpvExecutionModelIntersectionKHR ||
+                model == SpvExecutionModelAnyHitKHR ||
+                model == SpvExecutionModelClosestHitKHR ||
+                model == SpvExecutionModelMissKHR ||
+                model == SpvExecutionModelCallableKHR) {
+              if (message) {
+                *message =
+                    errorVUID +
+                    "in Vulkan evironment, Output Storage Class must not be "
+                    "used in GLCompute, RayGenerationKHR, IntersectionKHR, "
+                    "AnyHitKHR, ClosestHitKHR, MissKHR, or CallableKHR "
+                    "execution models";
+              }
+              return false;
+            }
+            return true;
+          });
+    }
+
+    if (storage_class == SpvStorageClassWorkgroup) {
+      std::string errorVUID = VkErrorID(4645);
+      function(consumer->function()->id())
+          ->RegisterExecutionModelLimitation([errorVUID](
+              SpvExecutionModel model, std::string* message) {
+            if (model != SpvExecutionModelGLCompute &&
+                model != SpvExecutionModelTaskNV &&
+                model != SpvExecutionModelMeshNV) {
+              if (message) {
+                *message =
+                    errorVUID +
+                    "in Vulkan evironment, Workgroup Storage Class is limited "
+                    "to MeshNV, TaskNV, and GLCompute execution model";
+              }
+              return false;
+            }
+            return true;
+          });
+    }
+  }
+}
+
 uint32_t ValidationState_t::getIdBound() const { return id_bound_; }
 
 void ValidationState_t::setIdBound(const uint32_t bound) { id_bound_ = bound; }
@@ -654,19 +732,19 @@
 
 bool ValidationState_t::IsVoidType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypeVoid;
+  return inst && inst->opcode() == SpvOpTypeVoid;
 }
 
 bool ValidationState_t::IsFloatScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypeFloat;
+  return inst && inst->opcode() == SpvOpTypeFloat;
 }
 
 bool ValidationState_t::IsFloatVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeVector) {
     return IsFloatScalarType(GetComponentType(id));
@@ -677,7 +755,9 @@
 
 bool ValidationState_t::IsFloatScalarOrVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeFloat) {
     return true;
@@ -692,13 +772,14 @@
 
 bool ValidationState_t::IsIntScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypeInt;
+  return inst && inst->opcode() == SpvOpTypeInt;
 }
 
 bool ValidationState_t::IsIntVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeVector) {
     return IsIntScalarType(GetComponentType(id));
@@ -709,7 +790,9 @@
 
 bool ValidationState_t::IsIntScalarOrVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeInt) {
     return true;
@@ -724,13 +807,14 @@
 
 bool ValidationState_t::IsUnsignedIntScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypeInt && inst->word(3) == 0;
+  return inst && inst->opcode() == SpvOpTypeInt && inst->word(3) == 0;
 }
 
 bool ValidationState_t::IsUnsignedIntVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeVector) {
     return IsUnsignedIntScalarType(GetComponentType(id));
@@ -741,13 +825,14 @@
 
 bool ValidationState_t::IsSignedIntScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypeInt && inst->word(3) == 1;
+  return inst && inst->opcode() == SpvOpTypeInt && inst->word(3) == 1;
 }
 
 bool ValidationState_t::IsSignedIntVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeVector) {
     return IsSignedIntScalarType(GetComponentType(id));
@@ -758,13 +843,14 @@
 
 bool ValidationState_t::IsBoolScalarType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypeBool;
+  return inst && inst->opcode() == SpvOpTypeBool;
 }
 
 bool ValidationState_t::IsBoolVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeVector) {
     return IsBoolScalarType(GetComponentType(id));
@@ -775,7 +861,9 @@
 
 bool ValidationState_t::IsBoolScalarOrVectorType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeBool) {
     return true;
@@ -790,7 +878,9 @@
 
 bool ValidationState_t::IsFloatMatrixType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
+  if (!inst) {
+    return false;
+  }
 
   if (inst->opcode() == SpvOpTypeMatrix) {
     return IsFloatScalarType(GetComponentType(id));
@@ -845,8 +935,7 @@
 
 bool ValidationState_t::IsPointerType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypePointer;
+  return inst && inst->opcode() == SpvOpTypePointer;
 }
 
 bool ValidationState_t::GetPointerTypeInfo(uint32_t id, uint32_t* data_type,
@@ -864,8 +953,7 @@
 
 bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const {
   const Instruction* inst = FindDef(id);
-  assert(inst);
-  return inst->opcode() == SpvOpTypeCooperativeMatrixNV;
+  return inst && inst->opcode() == SpvOpTypeCooperativeMatrixNV;
 }
 
 bool ValidationState_t::IsFloatCooperativeMatrixType(uint32_t id) const {
@@ -1187,16 +1275,13 @@
   return base_ptr;
 }
 
-bool ValidationState_t::ContainsSizedIntOrFloatType(uint32_t id, SpvOp type,
-                                                    uint32_t width) const {
-  if (type != SpvOpTypeInt && type != SpvOpTypeFloat) return false;
-
+bool ValidationState_t::ContainsType(
+    uint32_t id, const std::function<bool(const Instruction*)>& f,
+    bool traverse_all_types) const {
   const auto inst = FindDef(id);
   if (!inst) return false;
 
-  if (inst->opcode() == type) {
-    return inst->GetOperandAs<uint32_t>(1u) == width;
-  }
+  if (f(inst)) return true;
 
   switch (inst->opcode()) {
     case SpvOpTypeArray:
@@ -1206,24 +1291,45 @@
     case SpvOpTypeImage:
     case SpvOpTypeSampledImage:
     case SpvOpTypeCooperativeMatrixNV:
-      return ContainsSizedIntOrFloatType(inst->GetOperandAs<uint32_t>(1u), type,
-                                         width);
+      return ContainsType(inst->GetOperandAs<uint32_t>(1u), f,
+                          traverse_all_types);
     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;
+      if (traverse_all_types) {
+        return ContainsType(inst->GetOperandAs<uint32_t>(2u), f,
+                            traverse_all_types);
       }
-      return false;
-    }
+      break;
+    case SpvOpTypeFunction:
+    case SpvOpTypeStruct:
+      if (inst->opcode() == SpvOpTypeFunction && !traverse_all_types) {
+        return false;
+      }
+      for (uint32_t i = 1; i < inst->operands().size(); ++i) {
+        if (ContainsType(inst->GetOperandAs<uint32_t>(i), f,
+                         traverse_all_types)) {
+          return true;
+        }
+      }
+      break;
     default:
-      return false;
+      break;
   }
+
+  return false;
+}
+
+bool ValidationState_t::ContainsSizedIntOrFloatType(uint32_t id, SpvOp type,
+                                                    uint32_t width) const {
+  if (type != SpvOpTypeInt && type != SpvOpTypeFloat) return false;
+
+  const auto f = [type, width](const Instruction* inst) {
+    if (inst->opcode() == type) {
+      return inst->GetOperandAs<uint32_t>(1u) == width;
+    }
+    return false;
+  };
+  return ContainsType(id, f);
 }
 
 bool ValidationState_t::ContainsLimitedUseIntOrFloatType(uint32_t id) const {
@@ -1238,6 +1344,13 @@
   return false;
 }
 
+bool ValidationState_t::ContainsRuntimeArray(uint32_t id) const {
+  const auto f = [](const Instruction* inst) {
+    return inst->opcode() == SpvOpTypeRuntimeArray;
+  };
+  return ContainsType(id, f, /* traverse_all_types = */ false);
+}
+
 bool ValidationState_t::IsValidStorageClass(
     SpvStorageClass storage_class) const {
   if (spvIsVulkanEnv(context()->target_env)) {
@@ -1253,12 +1366,12 @@
       case SpvStorageClassFunction:
       case SpvStorageClassPushConstant:
       case SpvStorageClassPhysicalStorageBuffer:
-      case SpvStorageClassRayPayloadNV:
-      case SpvStorageClassIncomingRayPayloadNV:
-      case SpvStorageClassHitAttributeNV:
-      case SpvStorageClassCallableDataNV:
-      case SpvStorageClassIncomingCallableDataNV:
-      case SpvStorageClassShaderRecordBufferNV:
+      case SpvStorageClassRayPayloadKHR:
+      case SpvStorageClassIncomingRayPayloadKHR:
+      case SpvStorageClassHitAttributeKHR:
+      case SpvStorageClassCallableDataKHR:
+      case SpvStorageClassIncomingCallableDataKHR:
+      case SpvStorageClassShaderRecordBufferKHR:
         return true;
       default:
         return false;
@@ -1674,18 +1787,30 @@
       return VUID_WRAP(VUID-ShadingRateKHR-ShadingRateKHR-04492);
     case 4633:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04633);
+    case 4634:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04634);
     case 4635:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04635);
+    case 4636:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04636);
+    case 4637:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04637);
     case 4638:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04638);
     case 4639:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04639);
     case 4640:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04640);
+    case 4641:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04641);
     case 4642:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04642);
-    case 4649:
-      return VUID_WRAP(VUID-StandaloneSpirv-OpMemoryBarrier-04649);
+    case 4643:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04643);
+    case 4644:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04644);
+    case 4645:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04645);
     case 4651:
       return VUID_WRAP(VUID-StandaloneSpirv-OpVariable-04651);
     case 4652:
@@ -1694,25 +1819,57 @@
       return VUID_WRAP(VUID-StandaloneSpirv-OriginLowerLeft-04653);
     case 4654:
       return VUID_WRAP(VUID-StandaloneSpirv-PixelCenterInteger-04654);
+    case 4655:
+      return VUID_WRAP(VUID-StandaloneSpirv-UniformConstant-04655);
     case 4656:
       return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-04656);
     case 4657:
       return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-04657);
     case 4658:
       return VUID_WRAP(VUID-StandaloneSpirv-OpImageTexelPointer-04658);
+    case 4659:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpImageQuerySizeLod-04659);
+    case 4662:
+      return VUID_WRAP(VUID-StandaloneSpirv-Offset-04662);
+    case 4663:
+      return VUID_WRAP(VUID-StandaloneSpirv-Offset-04663);
+    case 4664:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpImageGather-04664);
+    case 4667:
+      return VUID_WRAP(VUID-StandaloneSpirv-None-04667);
     case 4669:
       return VUID_WRAP(VUID-StandaloneSpirv-GLSLShared-04669);
+    case 4670:
+      return VUID_WRAP(VUID-StandaloneSpirv-Flat-04670);
     case 4675:
       return VUID_WRAP(VUID-StandaloneSpirv-FPRoundingMode-04675);
+    case 4677:
+      return VUID_WRAP(VUID-StandaloneSpirv-Invariant-04677);
+    case 4682:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682);
+    case 6426:
+      return VUID_WRAP(VUID-StandaloneSpirv-LocalSize-06426); // formally 04683
     case 4685:
       return VUID_WRAP(VUID-StandaloneSpirv-OpGroupNonUniformBallotBitCount-04685);
     case 4686:
       return VUID_WRAP(VUID-StandaloneSpirv-None-04686);
+    case 4710:
+      return VUID_WRAP(VUID-StandaloneSpirv-PhysicalStorageBuffer64-04710);
     case 4711:
       return VUID_WRAP(VUID-StandaloneSpirv-OpTypeForwardPointer-04711);
+    case 4730:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpAtomicStore-04730);
+    case 4731:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpAtomicLoad-04731);
+    case 4732:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpMemoryBarrier-04732);
+    case 4733:
+      return VUID_WRAP(VUID-StandaloneSpirv-OpMemoryBarrier-04733);
+    case 4780:
+      return VUID_WRAP(VUID-StandaloneSpirv-Result-04780);
     default:
       return "";  // unknown id
-  };
+  }
   // clang-format on
 }
 
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index aeb1ca8..2ddfa4a 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -90,19 +90,6 @@
     // conversion opcodes
     bool use_int8_type = false;
 
-    // Use scalar block layout. See VK_EXT_scalar_block_layout:
-    // Defines scalar alignment:
-    // - scalar alignment equals the scalar size in bytes
-    // - array alignment is same as its element alignment
-    // - array alignment is max alignment of any of its members
-    // - vector alignment is same as component alignment
-    // - matrix alignment is same as component alignment
-    // For struct in Uniform, StorageBuffer, PushConstant:
-    // - Offset of a member is multiple of scalar alignment of that member
-    // - ArrayStride and MatrixStride are multiples of scalar alignment
-    // Members need not be listed in offset order
-    bool scalar_block_layout = false;
-
     // SPIR-V 1.4 allows us to select between any two composite values
     // of the same type.
     bool select_between_composites = false;
@@ -117,6 +104,9 @@
 
     // SPIR-V 1.4 allows Function and Private variables to be NonWritable
     bool nonwritable_var_in_function_or_private = false;
+
+    // Whether LocalSizeId execution mode is allowed by the environment.
+    bool env_allow_localsizeid = false;
   };
 
   ValidationState_t(const spv_const_context context,
@@ -461,6 +451,10 @@
   void RegisterSampledImageConsumer(uint32_t sampled_image_id,
                                     Instruction* consumer);
 
+  // Record a function's storage class consumer instruction
+  void RegisterStorageClassConsumer(SpvStorageClass storage_class,
+                                    Instruction* consumer);
+
   /// Returns the set of Global Variables.
   std::unordered_set<uint32_t>& global_vars() { return global_vars_; }
 
@@ -485,6 +479,12 @@
     return features_.env_relaxed_block_layout || options()->relax_block_layout;
   }
 
+  // Returns true if allowing localsizeid, either because the environment always
+  // allows it, or because it is enabled from the command-line.
+  bool IsLocalSizeIdAllowed() const {
+    return features_.env_allow_localsizeid || options()->allow_localsizeid;
+  }
+
   /// Sets the struct nesting depth for a given struct ID
   void set_struct_nesting_depth(uint32_t id, uint32_t depth) {
     struct_nesting_depth_[id] = depth;
@@ -587,6 +587,17 @@
   // 16-bit float that is not generally enabled for use.
   bool ContainsLimitedUseIntOrFloatType(uint32_t id) const;
 
+  // Returns true if |id| is a type that contains a runtime-sized array.
+  // Does not consider a pointers as contains the array.
+  bool ContainsRuntimeArray(uint32_t id) const;
+
+  // Generic type traversal.
+  // Only traverse pointers and functions if |traverse_all_types| is true.
+  // Recursively tests |f| against the type hierarchy headed by |id|.
+  bool ContainsType(uint32_t id,
+                    const std::function<bool(const Instruction*)>& f,
+                    bool traverse_all_types = true) const;
+
   // Gets value from OpConstant and OpSpecConstant as uint64.
   // Returns false on failure (no instruction, wrong instruction, not int).
   bool GetConstantValUint64(uint32_t id, uint64_t* val) const;
diff --git a/source/wasm/README.md b/source/wasm/README.md
new file mode 100644
index 0000000..aca0f70
--- /dev/null
+++ b/source/wasm/README.md
@@ -0,0 +1,43 @@
+# SPIRV-Tools
+
+Wasm (WebAssembly) build of https://github.com/KhronosGroup/SPIRV-Tools
+
+## Usage
+
+```js
+const spirvTools = require("spirv-tools");
+
+const test = async () => {
+  // Load the library
+  const spv = await spirvTools();
+
+  // assemble
+  const source = `
+             OpCapability Linkage 
+             OpCapability Shader 
+             OpMemoryModel Logical GLSL450 
+             OpSource GLSL 450 
+             OpDecorate %spec SpecId 1 
+      %int = OpTypeInt 32 1 
+     %spec = OpSpecConstant %int 0 
+    %const = OpConstant %int 42`;
+  const asResult = spv.as(
+    source,
+    spv.SPV_ENV_UNIVERSAL_1_3,
+    spv.SPV_TEXT_TO_BINARY_OPTION_NONE
+  );
+  console.log(`as returned ${asResult.byteLength} bytes`);
+
+  // re-disassemble
+  const disResult = spv.dis(
+    asResult,
+    spv.SPV_ENV_UNIVERSAL_1_3,
+    spv.SPV_BINARY_TO_TEXT_OPTION_INDENT |
+      spv.SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
+      spv.SPV_BINARY_TO_TEXT_OPTION_COLOR
+  );
+  console.log("dis:\n", disResult);
+};
+
+test();
+```
diff --git a/source/wasm/build.sh b/source/wasm/build.sh
new file mode 100755
index 0000000..f02ae52
--- /dev/null
+++ b/source/wasm/build.sh
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+# Copyright (c) 2020 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.
+
+set -e
+
+NUM_CORES=$(nproc)
+echo "Detected $NUM_CORES cores for building"
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+VERSION=$(sed -n '0,/^v20/ s/^v\(20[0-9.]*\).*/\1/p' $DIR/../../CHANGES).${GITHUB_RUN_NUMBER:-0}
+echo "Version: $VERSION"
+
+build() { 
+    type=$1
+    shift
+    args=$@
+    mkdir -p build/$type
+    pushd build/$type
+    echo $args
+    emcmake cmake \
+        -DCMAKE_BUILD_TYPE=Release \
+        $args \
+        ../..
+    emmake make -j $(( $NUM_CORES )) SPIRV-Tools-static
+
+    echo Building js interface
+    emcc \
+        --bind \
+        -I../../include \
+        -std=c++11 \
+        ../../source/wasm/spirv-tools.cpp \
+        source/libSPIRV-Tools.a \
+        -o spirv-tools.js \
+        -s MODULARIZE \
+        -Oz
+
+    popd
+    mkdir -p out/$type
+
+    # copy other js files
+    cp source/wasm/spirv-tools.d.ts out/$type/
+    sed -e 's/\("version"\s*:\s*\).*/\1"'$VERSION'",/' source/wasm/package.json > out/$type/package.json
+    cp source/wasm/README.md out/$type/
+    cp LICENSE out/$type/
+
+    cp build/$type/spirv-tools.js out/$type/
+    gzip -9 -k -f out/$type/spirv-tools.js
+    if [ -e build/$type/spirv-tools.wasm ] ; then
+       cp build/$type/spirv-tools.wasm out/$type/
+       gzip -9 -k -f out/$type/spirv-tools.wasm
+    fi
+}
+
+if [ ! -d external/spirv-headers ] ; then
+    echo "Fetching SPIRV-headers"
+    git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
+fi
+
+echo Building ${BASH_REMATCH[1]}
+build web\
+    -DSPIRV_COLOR_TERMINAL=OFF\
+    -DSPIRV_SKIP_TESTS=ON\
+    -DSPIRV_SKIP_EXECUTABLES=ON
+
+wc -c out/*/*
diff --git a/source/wasm/package.json b/source/wasm/package.json
new file mode 100644
index 0000000..7827353
--- /dev/null
+++ b/source/wasm/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "spirv-tools",
+  "version": "VERSION",
+  "license": "Apache-2.0",
+  "main": "spirv-tools",
+  "types": "spirv-tools.d.ts",
+  "files": [
+    "*.wasm",
+    "*.js",
+    "*.d.ts"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/KhronosGroup/SPIRV-Tools"
+  },
+  "homepage": "https://github.com/KhronosGroup/SPIRV-Tools"
+}
diff --git a/source/wasm/spirv-tools.cpp b/source/wasm/spirv-tools.cpp
new file mode 100644
index 0000000..90407f3
--- /dev/null
+++ b/source/wasm/spirv-tools.cpp
@@ -0,0 +1,93 @@
+// Copyright (c) 2020 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 "spirv-tools/libspirv.hpp"
+
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
+using namespace emscripten;
+
+void print_msg_to_stderr (spv_message_level_t, const char*,
+                          const spv_position_t&, const char* m) {
+  std::cerr << "error: " << m << std::endl;
+};
+
+std::string dis(std::string const& buffer, uint32_t env, uint32_t options) {
+  spvtools::SpirvTools core(static_cast<spv_target_env>(env));
+  core.SetMessageConsumer(print_msg_to_stderr);
+
+  std::vector<uint32_t> spirv;
+  const uint32_t* ptr = reinterpret_cast<const uint32_t*>(buffer.data());
+  spirv.assign(ptr, ptr + buffer.size() / 4);
+  std::string disassembly;
+  if (!core.Disassemble(spirv, &disassembly, options)) return "Error";
+  return disassembly;
+}
+
+emscripten::val as(std::string const& source, uint32_t env, uint32_t options) {
+  spvtools::SpirvTools core(static_cast<spv_target_env>(env));
+  core.SetMessageConsumer(print_msg_to_stderr);
+
+  std::vector<uint32_t> spirv;
+  if (!core.Assemble(source, &spirv, options)) spirv.clear();
+  const uint8_t* ptr = reinterpret_cast<const uint8_t*>(spirv.data());
+  return emscripten::val(emscripten::typed_memory_view(spirv.size() * 4,
+    ptr));
+}
+
+EMSCRIPTEN_BINDINGS(my_module) {
+  function("dis", &dis);
+  function("as", &as);
+  
+  constant("SPV_ENV_UNIVERSAL_1_0", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_0));
+  constant("SPV_ENV_VULKAN_1_0", static_cast<uint32_t>(SPV_ENV_VULKAN_1_0));
+  constant("SPV_ENV_UNIVERSAL_1_1", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_1));
+  constant("SPV_ENV_OPENCL_2_1", static_cast<uint32_t>(SPV_ENV_OPENCL_2_1));
+  constant("SPV_ENV_OPENCL_2_2", static_cast<uint32_t>(SPV_ENV_OPENCL_2_2));
+  constant("SPV_ENV_OPENGL_4_0", static_cast<uint32_t>(SPV_ENV_OPENGL_4_0));
+  constant("SPV_ENV_OPENGL_4_1", static_cast<uint32_t>(SPV_ENV_OPENGL_4_1));
+  constant("SPV_ENV_OPENGL_4_2", static_cast<uint32_t>(SPV_ENV_OPENGL_4_2));
+  constant("SPV_ENV_OPENGL_4_3", static_cast<uint32_t>(SPV_ENV_OPENGL_4_3));
+  constant("SPV_ENV_OPENGL_4_5", static_cast<uint32_t>(SPV_ENV_OPENGL_4_5));
+  constant("SPV_ENV_UNIVERSAL_1_2", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_2));
+  constant("SPV_ENV_OPENCL_1_2", static_cast<uint32_t>(SPV_ENV_OPENCL_1_2));
+  constant("SPV_ENV_OPENCL_EMBEDDED_1_2", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_1_2));
+  constant("SPV_ENV_OPENCL_2_0", static_cast<uint32_t>(SPV_ENV_OPENCL_2_0));
+  constant("SPV_ENV_OPENCL_EMBEDDED_2_0", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_2_0));
+  constant("SPV_ENV_OPENCL_EMBEDDED_2_1", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_2_1));
+  constant("SPV_ENV_OPENCL_EMBEDDED_2_2", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_2_2));
+  constant("SPV_ENV_UNIVERSAL_1_3", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_3));
+  constant("SPV_ENV_VULKAN_1_1", static_cast<uint32_t>(SPV_ENV_VULKAN_1_1));
+  constant("SPV_ENV_WEBGPU_0", static_cast<uint32_t>(SPV_ENV_WEBGPU_0));
+  constant("SPV_ENV_UNIVERSAL_1_4", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_4));
+  constant("SPV_ENV_VULKAN_1_1_SPIRV_1_4", static_cast<uint32_t>(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
+  constant("SPV_ENV_UNIVERSAL_1_5", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_5));
+  constant("SPV_ENV_VULKAN_1_2", static_cast<uint32_t>(SPV_ENV_VULKAN_1_2));
+
+
+  constant("SPV_BINARY_TO_TEXT_OPTION_NONE", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NONE));
+  constant("SPV_BINARY_TO_TEXT_OPTION_PRINT", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_PRINT));
+  constant("SPV_BINARY_TO_TEXT_OPTION_COLOR", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR));
+  constant("SPV_BINARY_TO_TEXT_OPTION_INDENT", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT));
+  constant("SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET));
+  constant("SPV_BINARY_TO_TEXT_OPTION_NO_HEADER", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER));
+  constant("SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES));
+
+  constant("SPV_TEXT_TO_BINARY_OPTION_NONE", static_cast<uint32_t>(SPV_TEXT_TO_BINARY_OPTION_NONE));
+  constant("SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS", static_cast<uint32_t>(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS));
+}
\ No newline at end of file
diff --git a/source/wasm/spirv-tools.d.ts b/source/wasm/spirv-tools.d.ts
new file mode 100644
index 0000000..9c19797
--- /dev/null
+++ b/source/wasm/spirv-tools.d.ts
@@ -0,0 +1,56 @@
+// Copyright (c) 2020 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.
+
+declare interface SpirvTools {
+  as(input: string, env: number, options: number): Uint8Array;
+  dis(input: Uint8Array, env: number, options: number): string;
+
+  SPV_ENV_UNIVERSAL_1_0: number;
+  SPV_ENV_VULKAN_1_0: number;
+  SPV_ENV_UNIVERSAL_1_1: number;
+  SPV_ENV_OPENCL_2_1: number;
+  SPV_ENV_OPENCL_2_2: number;
+  SPV_ENV_OPENGL_4_0: number;
+  SPV_ENV_OPENGL_4_1: number;
+  SPV_ENV_OPENGL_4_2: number;
+  SPV_ENV_OPENGL_4_3: number;
+  SPV_ENV_OPENGL_4_5: number;
+  SPV_ENV_UNIVERSAL_1_2: number;
+  SPV_ENV_OPENCL_1_2: number;
+  SPV_ENV_OPENCL_EMBEDDED_1_2: number;
+  SPV_ENV_OPENCL_2_0: number;
+  SPV_ENV_OPENCL_EMBEDDED_2_0: number;
+  SPV_ENV_OPENCL_EMBEDDED_2_1: number;
+  SPV_ENV_OPENCL_EMBEDDED_2_2: number;
+  SPV_ENV_UNIVERSAL_1_3: number;
+  SPV_ENV_VULKAN_1_1: number;
+  SPV_ENV_WEBGPU_0: number;
+  SPV_ENV_UNIVERSAL_1_4: number;
+  SPV_ENV_VULKAN_1_1_SPIRV_1_4: number;
+  SPV_ENV_UNIVERSAL_1_5: number;
+  SPV_ENV_VULKAN_1_2: number;
+
+  SPV_TEXT_TO_BINARY_OPTION_NONE: number;
+  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS: number;
+
+  SPV_BINARY_TO_TEXT_OPTION_NONE: number;
+  SPV_BINARY_TO_TEXT_OPTION_PRINT: number;
+  SPV_BINARY_TO_TEXT_OPTION_COLOR: number;
+  SPV_BINARY_TO_TEXT_OPTION_INDENT: number;
+  SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET: number;
+  SPV_BINARY_TO_TEXT_OPTION_NO_HEADER: number;
+  SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES: number;
+}
+
+export default function (): Promise<SpirvTools>;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 8ede58b..e88df04 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -186,9 +186,11 @@
 
 
 add_subdirectory(link)
+add_subdirectory(lint)
 add_subdirectory(opt)
 add_subdirectory(reduce)
 add_subdirectory(fuzz)
 add_subdirectory(tools)
 add_subdirectory(util)
 add_subdirectory(val)
+add_subdirectory(fuzzers)
diff --git a/test/binary_parse_test.cpp b/test/binary_parse_test.cpp
index 93e87bd..9a13f22 100644
--- a/test/binary_parse_test.cpp
+++ b/test/binary_parse_test.cpp
@@ -198,7 +198,7 @@
 
 class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> {
  protected:
-  ~BinaryParseTest() { spvDiagnosticDestroy(diagnostic_); }
+  ~BinaryParseTest() override { spvDiagnosticDestroy(diagnostic_); }
 
   void Parse(const SpirvVector& words, spv_result_t expected_result,
              bool flip_words = false) {
diff --git a/test/binary_to_text_test.cpp b/test/binary_to_text_test.cpp
index e8a02fd..df703e5 100644
--- a/test/binary_to_text_test.cpp
+++ b/test/binary_to_text_test.cpp
@@ -36,12 +36,12 @@
  public:
   BinaryToText()
       : context(spvContextCreate(SPV_ENV_UNIVERSAL_1_0)), binary(nullptr) {}
-  ~BinaryToText() {
+  ~BinaryToText() override {
     spvBinaryDestroy(binary);
     spvContextDestroy(context);
   }
 
-  virtual void SetUp() {
+  void SetUp() override {
     const char* textStr = R"(
       OpSource OpenCL_C 12
       OpMemoryModel Physical64 OpenCL
@@ -72,7 +72,7 @@
     ASSERT_EQ(SPV_SUCCESS, error);
   }
 
-  virtual void TearDown() {
+  void TearDown() override {
     spvBinaryDestroy(binary);
     binary = nullptr;
   }
@@ -386,7 +386,7 @@
             ::testing::ValuesIn(std::vector<std::string>{
                 "OpExecutionModeId %1 SubgroupsPerWorkgroupId %2\n",
                 "OpExecutionModeId %1 LocalSizeId %2 %3 %4\n",
-                "OpExecutionModeId %1 LocalSizeHintId %2\n",
+                "OpExecutionModeId %1 LocalSizeHintId %2 %3 %4\n",
                 "OpDecorateId %1 AlignmentId %2\n",
                 "OpDecorateId %1 MaxByteOffsetId %2\n",
             })));
diff --git a/test/enum_string_mapping_test.cpp b/test/enum_string_mapping_test.cpp
index 9bbd8ca..52aa653 100644
--- a/test/enum_string_mapping_test.cpp
+++ b/test/enum_string_mapping_test.cpp
@@ -181,6 +181,8 @@
          {SpvCapabilityDeviceGroup, "DeviceGroup"},
          {SpvCapabilityAtomicFloat32AddEXT, "AtomicFloat32AddEXT"},
          {SpvCapabilityAtomicFloat64AddEXT, "AtomicFloat64AddEXT"},
+         {SpvCapabilityAtomicFloat32MinMaxEXT, "AtomicFloat32MinMaxEXT"},
+         {SpvCapabilityAtomicFloat64MinMaxEXT, "AtomicFloat64MinMaxEXT"},
          {SpvCapabilityMultiView, "MultiView"},
          {SpvCapabilityInt64ImageEXT, "Int64ImageEXT"},
          {SpvCapabilitySampleMaskOverrideCoverageNV,
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 2e93293..56af0b9 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -17,6 +17,7 @@
   set(SOURCES
           fuzz_test_util.h
 
+          available_instructions_test.cpp
           call_graph_test.cpp
           comparator_deep_blocks_first_test.cpp
           data_synonym_transformation_test.cpp
@@ -30,6 +31,7 @@
           fuzzer_pass_construct_composites_test.cpp
           fuzzer_pass_donate_modules_test.cpp
           fuzzer_pass_outline_functions_test.cpp
+          fuzzerutil_test.cpp
           instruction_descriptor_test.cpp
           fuzzer_pass_test.cpp
           replayer_test.cpp
@@ -113,11 +115,14 @@
           transformation_store_test.cpp
           transformation_swap_commutable_operands_test.cpp
           transformation_swap_conditional_branch_operands_test.cpp
+          transformation_swap_function_variables_test.cpp
+          transformation_swap_two_functions_test.cpp
           transformation_toggle_access_chain_instruction_test.cpp
           transformation_record_synonymous_constants_test.cpp
           transformation_vector_shuffle_test.cpp
           transformation_wrap_early_terminator_in_function_test.cpp
           transformation_wrap_region_in_selection_test.cpp
+          transformation_wrap_vector_synonym_test.cpp
           uniform_buffer_element_descriptor_test.cpp)
 
   if (${SPIRV_ENABLE_LONG_FUZZER_TESTS})
diff --git a/test/fuzz/available_instructions_test.cpp b/test/fuzz/available_instructions_test.cpp
new file mode 100644
index 0000000..dc8a3b5
--- /dev/null
+++ b/test/fuzz/available_instructions_test.cpp
@@ -0,0 +1,328 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/available_instructions.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(AvailableInstructionsTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %7 %9
+         %15 = OpTypeVector %8 2
+         %16 = OpTypePointer Private %15
+         %17 = OpVariable %16 Private
+         %18 = OpConstant %8 1
+         %19 = OpConstant %8 2
+         %20 = OpConstantComposite %15 %18 %19
+         %21 = OpTypeVector %8 4
+         %22 = OpTypePointer Private %21
+         %23 = OpVariable %22 Private
+         %24 = OpConstant %8 10
+         %25 = OpConstant %8 20
+         %26 = OpConstant %8 30
+         %27 = OpConstant %8 40
+         %28 = OpConstantComposite %21 %24 %25 %26 %27
+         %31 = OpTypeInt 32 0
+         %32 = OpConstant %31 0
+         %33 = OpTypePointer Private %8
+         %41 = OpTypeBool
+         %46 = OpConstant %6 1
+         %54 = OpConstant %6 10
+         %57 = OpConstant %31 3
+         %61 = OpConstant %6 0
+         %66 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %55 = OpVariable %7 Function
+         %56 = OpVariable %9 Function
+         %65 = OpVariable %7 Function
+         %68 = OpVariable %7 Function
+               OpStore %17 %20
+               OpStore %23 %28
+               OpStore %55 %54
+         %58 = OpAccessChain %33 %23 %57
+         %59 = OpLoad %8 %58
+               OpStore %56 %59
+         %60 = OpFunctionCall %6 %13 %55 %56
+        %100 = OpCopyObject %21 %28
+         %62 = OpSGreaterThan %41 %60 %61
+               OpSelectionMerge %64 None
+               OpBranchConditional %62 %63 %67
+         %63 = OpLabel
+               OpStore %65 %66
+        %101 = OpCopyObject %21 %28
+               OpBranch %64
+         %67 = OpLabel
+               OpStore %68 %61
+               OpBranch %69
+         %69 = OpLabel
+               OpLoopMerge %71 %72 None
+               OpBranch %73
+         %73 = OpLabel
+         %74 = OpLoad %6 %68
+         %75 = OpSLessThan %41 %74 %54
+               OpBranchConditional %75 %70 %71
+         %70 = OpLabel
+         %76 = OpLoad %6 %65
+         %77 = OpIAdd %6 %76 %46
+               OpStore %65 %77
+               OpBranch %72
+         %72 = OpLabel
+         %78 = OpLoad %6 %68
+         %79 = OpIAdd %6 %78 %46
+               OpStore %68 %79
+               OpBranch %69
+         %71 = OpLabel
+        %102 = OpCopyObject %21 %28
+               OpBranch %64
+         %64 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %13 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %7
+         %12 = OpFunctionParameter %9
+         %14 = OpLabel
+         %29 = OpVariable %7 Function
+         %30 = OpLoad %6 %11
+         %34 = OpAccessChain %33 %17 %32
+         %35 = OpLoad %8 %34
+         %36 = OpConvertFToS %6 %35
+         %37 = OpIAdd %6 %30 %36
+               OpStore %29 %37
+         %38 = OpLoad %6 %11
+         %39 = OpLoad %8 %12
+         %40 = OpConvertFToS %6 %39
+         %42 = OpSLessThan %41 %38 %40
+        %103 = OpCopyObject %21 %28
+               OpSelectionMerge %44 None
+               OpBranchConditional %42 %43 %48
+         %43 = OpLabel
+         %45 = OpLoad %6 %29
+         %47 = OpIAdd %6 %45 %46
+               OpStore %29 %47
+               OpBranch %44
+         %48 = OpLabel
+         %49 = OpLoad %6 %29
+         %50 = OpISub %6 %49 %46
+               OpStore %29 %50
+               OpBranch %44
+         %44 = OpLabel
+         %51 = OpLoad %6 %29
+               OpReturnValue %51
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::Instruction* i1 = context->get_def_use_mgr()->GetDef(55);
+  opt::Instruction* i2 = context->get_def_use_mgr()->GetDef(101);
+  opt::Instruction* i3 = &*context->cfg()->block(67)->begin();
+  opt::Instruction* i4 = context->get_def_use_mgr()->GetDef(74);
+  opt::Instruction* i5 = context->get_def_use_mgr()->GetDef(102);
+  opt::Instruction* i6 = context->get_def_use_mgr()->GetDef(30);
+  opt::Instruction* i7 = context->get_def_use_mgr()->GetDef(47);
+  opt::Instruction* i8 = context->get_def_use_mgr()->GetDef(50);
+  opt::Instruction* i9 = context->get_def_use_mgr()->GetDef(51);
+
+  {
+    AvailableInstructions no_instructions(
+        context.get(),
+        [](opt::IRContext*, opt::Instruction*) -> bool { return false; });
+    for (auto i : {i1, i2, i3, i4, i5, i6, i7, i8, i9}) {
+      auto available = no_instructions.GetAvailableBeforeInstruction(i);
+      ASSERT_EQ(0, available.size());
+      ASSERT_TRUE(available.empty());
+    }
+  }
+  {
+    AvailableInstructions all_instructions(
+        context.get(),
+        [](opt::IRContext*, opt::Instruction*) -> bool { return true; });
+    {
+      auto available = all_instructions.GetAvailableBeforeInstruction(i1);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(30, available.size());
+      ASSERT_EQ(SpvOpTypeVoid, available[0]->opcode());
+      ASSERT_EQ(SpvOpVariable, available[15]->opcode());
+    }
+    {
+      auto available = all_instructions.GetAvailableBeforeInstruction(i2);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(46, available.size());
+      ASSERT_EQ(SpvOpTypeVoid, available[0]->opcode());
+      ASSERT_EQ(SpvOpTypePointer, available[3]->opcode());
+      ASSERT_EQ(SpvOpVariable, available[15]->opcode());
+      ASSERT_EQ(SpvOpFunctionCall, available[40]->opcode());
+      ASSERT_EQ(SpvOpStore, available[45]->opcode());
+    }
+    {
+      auto available = all_instructions.GetAvailableBeforeInstruction(i3);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(45, available.size());
+      ASSERT_EQ(SpvOpTypeVoid, available[0]->opcode());
+      ASSERT_EQ(SpvOpTypePointer, available[3]->opcode());
+      ASSERT_EQ(SpvOpVariable, available[15]->opcode());
+      ASSERT_EQ(SpvOpFunctionCall, available[40]->opcode());
+      ASSERT_EQ(SpvOpBranchConditional, available[44]->opcode());
+    }
+    {
+      auto available = all_instructions.GetAvailableBeforeInstruction(i6);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(33, available.size());
+      ASSERT_EQ(SpvOpTypeVoid, available[0]->opcode());
+      ASSERT_EQ(SpvOpTypeFloat, available[4]->opcode());
+      ASSERT_EQ(SpvOpTypePointer, available[8]->opcode());
+      ASSERT_EQ(SpvOpConstantComposite, available[12]->opcode());
+      ASSERT_EQ(SpvOpConstant, available[16]->opcode());
+      ASSERT_EQ(SpvOpFunctionParameter, available[30]->opcode());
+      ASSERT_EQ(SpvOpFunctionParameter, available[31]->opcode());
+      ASSERT_EQ(SpvOpVariable, available[32]->opcode());
+    }
+  }
+  {
+    AvailableInstructions vector_instructions(
+        context.get(),
+        [](opt::IRContext* ir_context, opt::Instruction* inst) -> bool {
+          return inst->type_id() != 0 && ir_context->get_type_mgr()
+                                                 ->GetType(inst->type_id())
+                                                 ->AsVector() != nullptr;
+        });
+    {
+      auto available = vector_instructions.GetAvailableBeforeInstruction(i4);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(3, available.size());
+      ASSERT_EQ(SpvOpConstantComposite, available[0]->opcode());
+      ASSERT_EQ(SpvOpConstantComposite, available[1]->opcode());
+      ASSERT_EQ(SpvOpCopyObject, available[2]->opcode());
+    }
+    {
+      auto available = vector_instructions.GetAvailableBeforeInstruction(i5);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(3, available.size());
+      ASSERT_EQ(SpvOpConstantComposite, available[0]->opcode());
+      ASSERT_EQ(SpvOpConstantComposite, available[1]->opcode());
+      ASSERT_EQ(SpvOpCopyObject, available[2]->opcode());
+    }
+    {
+      auto available = vector_instructions.GetAvailableBeforeInstruction(i6);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(2, available.size());
+      ASSERT_EQ(SpvOpConstantComposite, available[0]->opcode());
+      ASSERT_EQ(SpvOpConstantComposite, available[1]->opcode());
+    }
+  }
+  {
+    AvailableInstructions integer_add_instructions(
+        context.get(), [](opt::IRContext*, opt::Instruction* inst) -> bool {
+          return inst->opcode() == SpvOpIAdd;
+        });
+    {
+      auto available =
+          integer_add_instructions.GetAvailableBeforeInstruction(i7);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(1, available.size());
+      ASSERT_EQ(SpvOpIAdd, available[0]->opcode());
+    }
+    {
+      auto available =
+          integer_add_instructions.GetAvailableBeforeInstruction(i8);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(1, available.size());
+      ASSERT_EQ(SpvOpIAdd, available[0]->opcode());
+    }
+    {
+      auto available =
+          integer_add_instructions.GetAvailableBeforeInstruction(i9);
+      ASSERT_FALSE(available.empty());
+      ASSERT_EQ(1, available.size());
+      ASSERT_EQ(SpvOpIAdd, available[0]->opcode());
+    }
+  }
+}
+
+TEST(AvailableInstructionsTest, UnreachableBlock) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %8 "x"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+         %12 = OpLoad %6 %8
+               OpReturn
+         %10 = OpLabel
+         %11 = OpLoad %6 %8
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  AvailableInstructions all_instructions(
+      context.get(),
+      [](opt::IRContext*, opt::Instruction*) -> bool { return true; });
+  ASSERT_EQ(7, all_instructions
+                   .GetAvailableBeforeInstruction(
+                       context->get_def_use_mgr()->GetDef(12))
+                   .size());
+
+#ifndef NDEBUG
+  ASSERT_DEATH(all_instructions.GetAvailableBeforeInstruction(
+                   context->get_def_use_mgr()->GetDef(11)),
+               "Availability can only be queried for reachable instructions.");
+#endif
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzz_test_util.cpp b/test/fuzz/fuzz_test_util.cpp
index 28d3f89..bf0a4ff 100644
--- a/test/fuzz/fuzz_test_util.cpp
+++ b/test/fuzz/fuzz_test_util.cpp
@@ -160,13 +160,19 @@
     const Transformation& transformation, opt::IRContext* ir_context,
     TransformationContext* transformation_context,
     const std::unordered_set<uint32_t>& issued_overflow_ids) {
+  // To ensure that we cover all ToMessage and message-based constructor methods
+  // in our tests, we turn this into a message and back into a transformation,
+  // and use the reconstructed transformation in the rest of the function.
+  auto message = transformation.ToMessage();
+  auto reconstructed_transformation = Transformation::FromMessage(message);
+
   opt::analysis::DefUseManager::IdToDefMap before_transformation =
       ir_context->get_def_use_mgr()->id_to_defs();
-  transformation.Apply(ir_context, transformation_context);
+  reconstructed_transformation->Apply(ir_context, transformation_context);
   opt::analysis::DefUseManager::IdToDefMap after_transformation =
       ir_context->get_def_use_mgr()->id_to_defs();
   std::unordered_set<uint32_t> fresh_ids_for_transformation =
-      transformation.GetFreshIds();
+      reconstructed_transformation->GetFreshIds();
   for (auto& entry : after_transformation) {
     uint32_t id = entry.first;
     bool introduced_by_transformation_message =
diff --git a/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
index f7a0996..1045f8a 100644
--- a/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
+++ b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
@@ -128,13 +128,13 @@
                                                kConsoleMessageConsumer));
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassAddOpPhiSynonyms fuzzer_pass(context.get(), &transformation_context,
                                          &fuzzer_context,
-                                         &transformation_sequence);
+                                         &transformation_sequence, false);
 
   SetUpIdSynonyms(transformation_context.GetFactManager());
 
diff --git a/test/fuzz/fuzzer_pass_construct_composites_test.cpp b/test/fuzz/fuzzer_pass_construct_composites_test.cpp
index d49d1d6..a858e4c 100644
--- a/test/fuzz/fuzzer_pass_construct_composites_test.cpp
+++ b/test/fuzz/fuzzer_pass_construct_composites_test.cpp
@@ -77,7 +77,8 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
 
-  auto prng = MakeUnique<PseudoRandomGenerator>(0);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
 
   for (uint32_t i = 0; i < 10; i++) {
     const auto context =
@@ -87,12 +88,11 @@
         context.get(), validator_options, kConsoleMessageConsumer));
     TransformationContext transformation_context(
         MakeUnique<FactManager>(context.get()), validator_options);
-    FuzzerContext fuzzer_context(prng.get(), 100);
     protobufs::TransformationSequence transformation_sequence;
 
     FuzzerPassConstructComposites fuzzer_pass(
         context.get(), &transformation_context, &fuzzer_context,
-        &transformation_sequence);
+        &transformation_sequence, false);
 
     fuzzer_pass.Apply();
 
@@ -158,7 +158,8 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
 
-  auto prng = MakeUnique<PseudoRandomGenerator>(0);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
 
   for (uint32_t i = 0; i < 10; i++) {
     const auto context =
@@ -168,12 +169,11 @@
         context.get(), validator_options, kConsoleMessageConsumer));
     TransformationContext transformation_context(
         MakeUnique<FactManager>(context.get()), validator_options);
-    FuzzerContext fuzzer_context(prng.get(), 100);
     protobufs::TransformationSequence transformation_sequence;
 
     FuzzerPassConstructComposites fuzzer_pass(
         context.get(), &transformation_context, &fuzzer_context,
-        &transformation_sequence);
+        &transformation_sequence, false);
 
     fuzzer_pass.Apply();
 
diff --git a/test/fuzz/fuzzer_pass_donate_modules_test.cpp b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
index 1a7cd4a..fe8e671 100644
--- a/test/fuzz/fuzzer_pass_donate_modules_test.cpp
+++ b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
@@ -204,13 +204,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -285,13 +285,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -416,13 +416,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -511,13 +511,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -531,6 +531,7 @@
   std::string recipient_shader = R"(
                OpCapability Shader
                OpCapability ImageQuery
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -548,6 +549,7 @@
   std::string donor_shader = R"(
                OpCapability Shader
                OpCapability ImageQuery
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -581,13 +583,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -709,13 +711,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -805,13 +807,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -937,13 +939,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -1073,13 +1075,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -1155,13 +1157,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), true);
 
@@ -1242,13 +1244,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -1346,13 +1348,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), true);
 
@@ -1418,13 +1420,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), true);
 
@@ -1528,13 +1530,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), true);
 
@@ -1712,13 +1714,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator rng(0);
-  FuzzerContext fuzzer_context(&rng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -1784,13 +1786,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -1941,13 +1943,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator rng(0);
-  FuzzerContext fuzzer_context(&rng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
@@ -2014,13 +2016,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator rng(0);
-  FuzzerContext fuzzer_context(&rng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   ASSERT_TRUE(donor_context->get_feature_mgr()->HasCapability(
       SpvCapabilityVariablePointersStorageBuffer));
@@ -2247,13 +2249,13 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
                                       &transformation_context, &fuzzer_context,
-                                      &transformation_sequence, {});
+                                      &transformation_sequence, false, {});
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), true);
 
diff --git a/test/fuzz/fuzzer_pass_outline_functions_test.cpp b/test/fuzz/fuzzer_pass_outline_functions_test.cpp
index 576962c..a088e17 100644
--- a/test/fuzz/fuzzer_pass_outline_functions_test.cpp
+++ b/test/fuzz/fuzzer_pass_outline_functions_test.cpp
@@ -124,13 +124,13 @@
                                                kConsoleMessageConsumer));
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
                                          &fuzzer_context,
-                                         &transformation_sequence);
+                                         &transformation_sequence, false);
 
   // Block 28
   auto suitable_entry_block =
@@ -167,13 +167,13 @@
                                                kConsoleMessageConsumer));
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
                                          &fuzzer_context,
-                                         &transformation_sequence);
+                                         &transformation_sequence, false);
 
   // Block 20
   auto suitable_entry_block =
@@ -291,13 +291,13 @@
                                                kConsoleMessageConsumer));
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
                                          &fuzzer_context,
-                                         &transformation_sequence);
+                                         &transformation_sequence, false);
 
   // Block 21
   auto suitable_entry_block =
@@ -458,13 +458,13 @@
                                                kConsoleMessageConsumer));
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformation_sequence;
 
   FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
                                          &fuzzer_context,
-                                         &transformation_sequence);
+                                         &transformation_sequence, false);
 
   // Block 39 is not a merge block, so it is already suitable.
   auto suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining(
diff --git a/test/fuzz/fuzzer_pass_test.cpp b/test/fuzz/fuzzer_pass_test.cpp
index 283aa11..31b8582 100644
--- a/test/fuzz/fuzzer_pass_test.cpp
+++ b/test/fuzz/fuzzer_pass_test.cpp
@@ -29,7 +29,7 @@
                  FuzzerContext* fuzzer_context,
                  protobufs::TransformationSequence* transformations)
       : FuzzerPass(ir_context, transformation_context, fuzzer_context,
-                   transformations) {}
+                   transformations, false) {}
 
   ~FuzzerPassMock() override = default;
 
@@ -87,8 +87,8 @@
   ASSERT_TRUE(dominator_analysis->IsReachable(5));
   ASSERT_FALSE(dominator_analysis->IsReachable(8));
 
-  PseudoRandomGenerator prng(0);
-  FuzzerContext fuzzer_context(&prng, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   protobufs::TransformationSequence transformations;
   FuzzerPassMock fuzzer_pass_mock(context.get(), &transformation_context,
                                   &fuzzer_context, &transformations);
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index dc90574..2a22e6a 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -12,12 +12,11 @@
 // 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 "gtest/gtest.h"
+#include "source/fuzz/fuzzer.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/replayer.h"
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -1597,6 +1596,23 @@
                OpFunctionEnd
   )";
 
+// A virtually empty piece of SPIR-V.
+
+const std::string kTestShader7 = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
 void AddConstantUniformFact(protobufs::FactSequence* facts,
                             uint32_t descriptor_set, uint32_t binding,
                             std::vector<uint32_t>&& indices, uint32_t value) {
@@ -1642,37 +1658,53 @@
     });
   }
 
-  std::vector<Fuzzer::RepeatedPassStrategy> strategies{
-      Fuzzer::RepeatedPassStrategy::kSimple,
-      Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations,
-      Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations};
+  std::vector<RepeatedPassStrategy> strategies{
+      RepeatedPassStrategy::kSimple,
+      RepeatedPassStrategy::kLoopedWithRecommendations,
+      RepeatedPassStrategy::kRandomWithRecommendations};
   uint32_t strategy_index = 0;
   for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
     spvtools::ValidatorOptions validator_options;
+
+    std::unique_ptr<opt::IRContext> ir_context;
+    ASSERT_TRUE(fuzzerutil::BuildIRContext(env, kConsoleMessageConsumer,
+                                           binary_in, validator_options,
+                                           &ir_context));
+
+    auto fuzzer_context = MakeUnique<FuzzerContext>(
+        MakeUnique<PseudoRandomGenerator>(seed),
+        FuzzerContext::GetMinFreshId(ir_context.get()), false);
+
+    auto transformation_context = MakeUnique<TransformationContext>(
+        MakeUnique<FactManager>(ir_context.get()), validator_options);
+    transformation_context->GetFactManager()->AddInitialFacts(
+        kConsoleMessageConsumer, initial_facts);
+
     // Every 4th time we run the fuzzer, enable all fuzzer passes.
     bool enable_all_passes = (seed % 4) == 0;
-    auto fuzzer_result =
-        Fuzzer(env, kConsoleMessageConsumer, binary_in, initial_facts,
-               donor_suppliers, MakeUnique<PseudoRandomGenerator>(seed),
-               enable_all_passes, strategies[strategy_index], true,
-               validator_options)
-            .Run();
+    Fuzzer fuzzer(std::move(ir_context), std::move(transformation_context),
+                  std::move(fuzzer_context), kConsoleMessageConsumer,
+                  donor_suppliers, enable_all_passes,
+                  strategies[strategy_index], true, validator_options, false);
+    auto fuzzer_result = fuzzer.Run(0);
 
     // Cycle the repeated pass strategy so that we try a different one next time
     // we run the fuzzer.
     strategy_index =
         (strategy_index + 1) % static_cast<uint32_t>(strategies.size());
 
-    ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result.status);
-    ASSERT_TRUE(t.Validate(fuzzer_result.transformed_binary));
+    ASSERT_NE(Fuzzer::Status::kFuzzerPassLedToInvalidModule,
+              fuzzer_result.status);
+    std::vector<uint32_t> transformed_binary;
+    fuzzer.GetIRContext()->module()->ToBinary(&transformed_binary, true);
+    ASSERT_TRUE(t.Validate(transformed_binary));
 
     auto replayer_result =
-        Replayer(
-            env, kConsoleMessageConsumer, binary_in, initial_facts,
-            fuzzer_result.applied_transformations,
-            static_cast<uint32_t>(
-                fuzzer_result.applied_transformations.transformation_size()),
-            false, validator_options)
+        Replayer(env, kConsoleMessageConsumer, binary_in, initial_facts,
+                 fuzzer.GetTransformationSequence(),
+                 static_cast<uint32_t>(
+                     fuzzer.GetTransformationSequence().transformation_size()),
+                 false, validator_options)
             .Run();
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
               replayer_result.status);
@@ -1682,12 +1714,12 @@
     // replay should be identical to that which resulted from fuzzing.
     std::string fuzzer_transformations_string;
     std::string replayer_transformations_string;
-    fuzzer_result.applied_transformations.SerializeToString(
+    fuzzer.GetTransformationSequence().SerializeToString(
         &fuzzer_transformations_string);
     replayer_result.applied_transformations.SerializeToString(
         &replayer_transformations_string);
     ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
-    ASSERT_TRUE(IsEqual(env, fuzzer_result.transformed_binary,
+    ASSERT_TRUE(IsEqual(env, transformed_binary,
                         replayer_result.transformed_module.get()));
   }
 }
@@ -1745,6 +1777,13 @@
                        kNumFuzzerRuns);
 }
 
+TEST(FuzzerReplayerTest, Miscellaneous7) {
+  // Do some fuzzer runs, starting from an initial seed of 1 (seed value chosen
+  // arbitrarily).
+  RunFuzzerAndReplayer(kTestShader7, protobufs::FactSequence(), 1,
+                       kNumFuzzerRuns);
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
index 6d9dad3..acee03c 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -12,15 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "source/fuzz/fuzzer.h"
-#include "source/fuzz/shrinker.h"
-
 #include <functional>
 #include <vector>
 
 #include "gtest/gtest.h"
+#include "source/fuzz/fuzzer.h"
 #include "source/fuzz/fuzzer_util.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"
 
@@ -1044,24 +1043,38 @@
   // Depending on the seed, decide whether to enable all passes and which
   // repeated pass manager to use.
   bool enable_all_passes = (seed % 4) == 0;
-  Fuzzer::RepeatedPassStrategy repeated_pass_strategy;
+  RepeatedPassStrategy repeated_pass_strategy;
   if ((seed % 3) == 0) {
-    repeated_pass_strategy = Fuzzer::RepeatedPassStrategy::kSimple;
+    repeated_pass_strategy = RepeatedPassStrategy::kSimple;
   } else if ((seed % 3) == 1) {
-    repeated_pass_strategy =
-        Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations;
+    repeated_pass_strategy = RepeatedPassStrategy::kLoopedWithRecommendations;
   } else {
-    repeated_pass_strategy =
-        Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations;
+    repeated_pass_strategy = RepeatedPassStrategy::kRandomWithRecommendations;
   }
 
-  auto fuzzer_result =
-      Fuzzer(env, kConsoleMessageConsumer, binary_in, initial_facts,
-             donor_suppliers, MakeUnique<PseudoRandomGenerator>(seed),
-             enable_all_passes, repeated_pass_strategy, true, validator_options)
-          .Run();
-  ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result.status);
-  ASSERT_TRUE(t.Validate(fuzzer_result.transformed_binary));
+  std::unique_ptr<opt::IRContext> ir_context;
+  ASSERT_TRUE(fuzzerutil::BuildIRContext(
+      env, kConsoleMessageConsumer, binary_in, validator_options, &ir_context));
+
+  auto fuzzer_context = MakeUnique<FuzzerContext>(
+      MakeUnique<PseudoRandomGenerator>(seed),
+      FuzzerContext::GetMinFreshId(ir_context.get()), false);
+
+  auto transformation_context = MakeUnique<TransformationContext>(
+      MakeUnique<FactManager>(ir_context.get()), validator_options);
+  transformation_context->GetFactManager()->AddInitialFacts(
+      kConsoleMessageConsumer, initial_facts);
+
+  Fuzzer fuzzer(std::move(ir_context), std::move(transformation_context),
+                std::move(fuzzer_context), kConsoleMessageConsumer,
+                donor_suppliers, enable_all_passes, repeated_pass_strategy,
+                true, validator_options, false);
+  auto fuzzer_result = fuzzer.Run(0);
+  ASSERT_NE(Fuzzer::Status::kFuzzerPassLedToInvalidModule,
+            fuzzer_result.status);
+  std::vector<uint32_t> transformed_binary;
+  fuzzer.GetIRContext()->module()->ToBinary(&transformed_binary, true);
+  ASSERT_TRUE(t.Validate(transformed_binary));
 
   const uint32_t kReasonableStepLimit = 50;
   const uint32_t kSmallStepLimit = 20;
@@ -1069,30 +1082,30 @@
   // With the AlwaysInteresting test, we should quickly shrink to the original
   // binary with no transformations remaining.
   RunAndCheckShrinker(env, binary_in, initial_facts,
-                      fuzzer_result.applied_transformations,
+                      fuzzer.GetTransformationSequence(),
                       AlwaysInteresting().AsFunction(), binary_in, 0,
                       kReasonableStepLimit, validator_options);
 
   // With the OnlyInterestingFirstTime test, no shrinking should be achieved.
   RunAndCheckShrinker(
-      env, binary_in, initial_facts, fuzzer_result.applied_transformations,
-      OnlyInterestingFirstTime().AsFunction(), fuzzer_result.transformed_binary,
+      env, binary_in, initial_facts, fuzzer.GetTransformationSequence(),
+      OnlyInterestingFirstTime().AsFunction(), transformed_binary,
       static_cast<uint32_t>(
-          fuzzer_result.applied_transformations.transformation_size()),
+          fuzzer.GetTransformationSequence().transformation_size()),
       kReasonableStepLimit, validator_options);
 
   // 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_result.applied_transformations,
+      env, binary_in, initial_facts, fuzzer.GetTransformationSequence(),
       PingPong().AsFunction(), {}, 0, kSmallStepLimit, validator_options);
 
   // 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_result.applied_transformations,
+      env, binary_in, initial_facts, fuzzer.GetTransformationSequence(),
       InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
       kSmallStepLimit, validator_options);
 }
diff --git a/test/fuzz/fuzzerutil_test.cpp b/test/fuzz/fuzzerutil_test.cpp
new file mode 100644
index 0000000..0ad3e74
--- /dev/null
+++ b/test/fuzz/fuzzerutil_test.cpp
@@ -0,0 +1,1807 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(FuzzerUtilMaybeFindBlockTest, 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
+               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_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Only blocks with id 11 and 12 can be found.
+  // Should return nullptr when id is not a label or id was not found.
+  uint32_t block_id1 = 11;
+  uint32_t block_id2 = 12;
+  uint32_t block_id3 = 13;
+  uint32_t block_id4 = 8;
+
+  opt::IRContext* ir_context = context.get();
+  // Block with id 11 should be found.
+  ASSERT_TRUE(fuzzerutil::MaybeFindBlock(ir_context, block_id1) != nullptr);
+  // Block with id 12 should be found.
+  ASSERT_TRUE(fuzzerutil::MaybeFindBlock(ir_context, block_id2) != nullptr);
+  // Block with id 13 cannot be found.
+  ASSERT_FALSE(fuzzerutil::MaybeFindBlock(ir_context, block_id3) != nullptr);
+  // Block with id 8 exisits but don't not of type OpLabel.
+  ASSERT_FALSE(fuzzerutil::MaybeFindBlock(ir_context, block_id4) != nullptr);
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetBoolConstantTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %36
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b1"
+               OpName %10 "b2"
+               OpName %12 "b3"
+               OpName %13 "b4"
+               OpName %16 "f1"
+               OpName %18 "f2"
+               OpName %20 "cf1"
+               OpName %22 "cf2"
+               OpName %26 "i1"
+               OpName %28 "i2"
+               OpName %30 "ci1"
+               OpName %32 "ci2"
+               OpName %36 "value"
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %32 RelaxedPrecision
+               OpDecorate %36 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 1.23000002
+         %19 = OpConstant %14 1.11000001
+         %21 = OpConstant %14 2
+         %23 = OpConstant %14 3.29999995
+         %24 = OpTypeInt 32 1
+         %25 = OpTypePointer Function %24
+         %27 = OpConstant %24 1
+         %29 = OpConstant %24 100
+         %31 = OpConstant %24 123
+         %33 = OpConstant %24 1111
+         %35 = OpTypePointer Input %14
+         %36 = OpVariable %35 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %13 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %18 = OpVariable %15 Function
+         %20 = OpVariable %15 Function
+         %22 = OpVariable %15 Function
+         %26 = OpVariable %25 Function
+         %28 = OpVariable %25 Function
+         %30 = OpVariable %25 Function
+         %32 = OpVariable %25 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %9
+               OpStore %13 %11
+               OpStore %16 %17
+               OpStore %18 %19
+               OpStore %20 %21
+               OpStore %22 %23
+               OpStore %26 %27
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  opt::IRContext* ir_context = context.get();
+  // A bool constant with value false exists and the id is 11.
+  ASSERT_EQ(11, fuzzerutil::MaybeGetBoolConstant(
+                    ir_context, transformation_context, false, false));
+  // A bool constant with value true exists and the id is 9.
+  ASSERT_EQ(9, fuzzerutil::MaybeGetBoolConstant(
+                   ir_context, transformation_context, true, false));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetBoolTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+  // A bool type with result id of 34 exists.
+  ASSERT_TRUE(fuzzerutil::MaybeGetBoolType(ir_context));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetCompositeConstantTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %54
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b1"
+               OpName %10 "b2"
+               OpName %12 "b3"
+               OpName %13 "b4"
+               OpName %16 "f1"
+               OpName %18 "f2"
+               OpName %22 "zc"
+               OpName %24 "i1"
+               OpName %28 "i2"
+               OpName %30 "i3"
+               OpName %32 "i4"
+               OpName %37 "f_arr"
+               OpName %47 "i_arr"
+               OpName %54 "value"
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %32 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %54 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 1.23000002
+         %19 = OpConstant %14 1.11000001
+         %20 = OpTypeInt 32 1
+         %21 = OpTypePointer Function %20
+         %23 = OpConstant %20 0
+         %25 = OpConstant %20 1
+         %26 = OpTypeInt 32 0
+         %27 = OpTypePointer Function %26
+         %29 = OpConstant %26 100
+         %31 = OpConstant %20 -1
+         %33 = OpConstant %20 -99
+         %34 = OpConstant %26 5
+         %35 = OpTypeArray %14 %34
+         %36 = OpTypePointer Function %35
+         %38 = OpConstant %14 5.5
+         %39 = OpConstant %14 4.4000001
+         %40 = OpConstant %14 3.29999995
+         %41 = OpConstant %14 2.20000005
+         %42 = OpConstant %14 1.10000002
+         %43 = OpConstantComposite %35 %38 %39 %40 %41 %42
+         %44 = OpConstant %26 3
+         %45 = OpTypeArray %20 %44
+         %46 = OpTypePointer Function %45
+         %48 = OpConstant %20 3
+         %49 = OpConstant %20 7
+         %50 = OpConstant %20 9
+         %51 = OpConstantComposite %45 %48 %49 %50
+         %53 = OpTypePointer Input %14
+         %54 = OpVariable %53 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %13 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %18 = OpVariable %15 Function
+         %22 = OpVariable %21 Function
+         %24 = OpVariable %21 Function
+         %28 = OpVariable %27 Function
+         %30 = OpVariable %21 Function
+         %32 = OpVariable %21 Function
+         %37 = OpVariable %36 Function
+         %47 = OpVariable %46 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %9
+               OpStore %13 %11
+               OpStore %16 %17
+               OpStore %18 %19
+               OpStore %22 %23
+               OpStore %24 %25
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpStore %37 %43
+               OpStore %47 %51
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  opt::IRContext* ir_context = context.get();
+
+  //      %43 = OpConstantComposite %35 %38 %39 %40 %41 %42
+  //    %51 = OpConstantComposite %45 %48 %49 %50
+  // This should pass as a float array with 5 elements exist and its id is 43.
+  ASSERT_EQ(43, fuzzerutil::MaybeGetCompositeConstant(
+                    ir_context, transformation_context, {38, 39, 40, 41, 42},
+                    35, false));
+  // This should pass as an int array with 3 elements exist and its id is 51.
+  ASSERT_EQ(51,
+            fuzzerutil::MaybeGetCompositeConstant(
+                ir_context, transformation_context, {48, 49, 50}, 45, false));
+  // An int array with 2 elements does not exist.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetCompositeConstant(
+                   ir_context, transformation_context, {48, 49}, 45, false));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetFloatConstantTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %36
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b1"
+               OpName %10 "b2"
+               OpName %12 "b3"
+               OpName %13 "b4"
+               OpName %16 "f1"
+               OpName %18 "f2"
+               OpName %20 "cf1"
+               OpName %22 "cf2"
+               OpName %26 "i1"
+               OpName %28 "i2"
+               OpName %30 "ci1"
+               OpName %32 "ci2"
+               OpName %36 "value"
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %32 RelaxedPrecision
+               OpDecorate %36 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 1.23000002
+         %19 = OpConstant %14 1.11000001
+         %21 = OpConstant %14 2
+         %23 = OpConstant %14 3.29999995
+         %24 = OpTypeInt 32 1
+         %25 = OpTypePointer Function %24
+         %27 = OpConstant %24 1
+         %29 = OpConstant %24 100
+         %31 = OpConstant %24 123
+         %33 = OpConstant %24 1111
+         %35 = OpTypePointer Input %14
+         %36 = OpVariable %35 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %13 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %18 = OpVariable %15 Function
+         %20 = OpVariable %15 Function
+         %22 = OpVariable %15 Function
+         %26 = OpVariable %25 Function
+         %28 = OpVariable %25 Function
+         %30 = OpVariable %25 Function
+         %32 = OpVariable %25 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %9
+               OpStore %13 %11
+               OpStore %16 %17
+               OpStore %18 %19
+               OpStore %20 %21
+               OpStore %22 %23
+               OpStore %26 %27
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  opt::IRContext* ir_context = context.get();
+
+  uint32_t word1 = fuzzerutil::FloatToWord(2);
+  uint32_t word2 = fuzzerutil::FloatToWord(1.23f);
+
+  // A 32 bit float constant of value 2 exists and its id is 21.
+  ASSERT_EQ(21, fuzzerutil::MaybeGetFloatConstant(
+                    ir_context, transformation_context,
+                    std::vector<uint32_t>{word1}, 32, false));
+  // A 32 bit float constant of value 1.23 exists and its id is 17.
+  ASSERT_EQ(17, fuzzerutil::MaybeGetFloatConstant(
+                    ir_context, transformation_context,
+                    std::vector<uint32_t>{word2}, 32, false));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetFloatTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+  // A float type with width = 32 and result id of 7 exists.
+  ASSERT_EQ(7, fuzzerutil::MaybeGetFloatType(ir_context, 32));
+
+  // A float int type with width = 32 exists, but the id should be 7.
+  ASSERT_NE(5, fuzzerutil::MaybeGetFloatType(ir_context, 32));
+
+  // A float type with width 30 does not exist.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetFloatType(ir_context, 30));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetIntegerConstantFromValueAndTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %36
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b1"
+               OpName %10 "b2"
+               OpName %12 "b3"
+               OpName %13 "b4"
+               OpName %16 "f1"
+               OpName %18 "f2"
+               OpName %22 "zc"
+               OpName %24 "i1"
+               OpName %28 "i2"
+               OpName %30 "i3"
+               OpName %32 "i4"
+               OpName %36 "value"
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %32 RelaxedPrecision
+               OpDecorate %36 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 1.23000002
+         %19 = OpConstant %14 1.11000001
+         %20 = OpTypeInt 32 1
+         %21 = OpTypePointer Function %20
+         %23 = OpConstant %20 0
+         %25 = OpConstant %20 1
+         %26 = OpTypeInt 32 0
+         %27 = OpTypePointer Function %26
+         %29 = OpConstant %26 100
+         %31 = OpConstant %20 -1
+         %33 = OpConstant %20 -99
+         %35 = OpTypePointer Input %14
+         %36 = OpVariable %35 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %13 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %18 = OpVariable %15 Function
+         %22 = OpVariable %21 Function
+         %24 = OpVariable %21 Function
+         %28 = OpVariable %27 Function
+         %30 = OpVariable %21 Function
+         %32 = OpVariable %21 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %9
+               OpStore %13 %11
+               OpStore %16 %17
+               OpStore %18 %19
+               OpStore %22 %23
+               OpStore %24 %25
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+
+  // A 32 bit signed int constant (with int type id 20) with value 1 exists and
+  // the id is 25.
+  ASSERT_EQ(25, fuzzerutil::MaybeGetIntegerConstantFromValueAndType(ir_context,
+                                                                    1, 20));
+  // A 32 bit unsigned int constant (with int type id 0) with value 100 exists
+  // and the id is 29.
+  ASSERT_EQ(29, fuzzerutil::MaybeGetIntegerConstantFromValueAndType(ir_context,
+                                                                    100, 26));
+  // A 32 bit unsigned int constant with value 50 does not exist.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetIntegerConstantFromValueAndType(ir_context,
+                                                                   50, 26));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetIntegerConstantTest) {
+  std::string shader = R"(
+OpCapability Shader
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %36
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b1"
+               OpName %10 "b2"
+               OpName %12 "b3"
+               OpName %13 "b4"
+               OpName %16 "f1"
+               OpName %18 "f2"
+               OpName %22 "zc"
+               OpName %24 "i1"
+               OpName %28 "i2"
+               OpName %30 "i3"
+               OpName %32 "i4"
+               OpName %36 "value"
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %32 RelaxedPrecision
+               OpDecorate %36 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 1.23000002
+         %19 = OpConstant %14 1.11000001
+         %20 = OpTypeInt 32 1
+         %21 = OpTypePointer Function %20
+         %23 = OpConstant %20 0
+         %25 = OpConstant %20 1
+         %26 = OpTypeInt 32 0
+         %27 = OpTypePointer Function %26
+         %29 = OpConstant %26 100
+         %31 = OpConstant %20 -1
+         %33 = OpConstant %20 -99
+         %35 = OpTypePointer Input %14
+         %36 = OpVariable %35 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %13 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %18 = OpVariable %15 Function
+         %22 = OpVariable %21 Function
+         %24 = OpVariable %21 Function
+         %28 = OpVariable %27 Function
+         %30 = OpVariable %21 Function
+         %32 = OpVariable %21 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %9
+               OpStore %13 %11
+               OpStore %16 %17
+               OpStore %18 %19
+               OpStore %22 %23
+               OpStore %24 %25
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  opt::IRContext* ir_context = context.get();
+
+  // A 32 bit unsigned int constant with value 1 exists and the id is 25.
+  ASSERT_EQ(25, fuzzerutil::MaybeGetIntegerConstant(
+                    ir_context, transformation_context,
+                    std::vector<uint32_t>{1}, 32, true, false));
+  // A 32 bit unsigned int constant with value 100 exists and the id is 29.
+  ASSERT_EQ(29, fuzzerutil::MaybeGetIntegerConstant(
+                    ir_context, transformation_context,
+                    std::vector<uint32_t>{100}, 32, false, false));
+  // A 32 bit signed int constant with value 99 doesn't not exist and should
+  // return 0.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetIntegerConstant(
+                   ir_context, transformation_context,
+                   std::vector<uint32_t>{99}, 32, true, false));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetIntegerTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+
+  // A signed int type with width = 32 and result id of 6 exists.
+  ASSERT_EQ(6, fuzzerutil::MaybeGetIntegerType(ir_context, 32, true));
+
+  // A signed int type with width = 32 exists, but the id should be 6.
+  ASSERT_FALSE(fuzzerutil::MaybeGetIntegerType(ir_context, 32, true) == 5);
+
+  // A int type with width = 32 and result id of 6 exists, but it should be a
+  // signed int.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetIntegerType(ir_context, 32, false));
+  // A signed int type with width 30 does not exist.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetIntegerType(ir_context, 30, true));
+  // An unsigned int type with width 22 does not exist.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetIntegerType(ir_context, 22, false));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetPointerTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+  auto private_storage_class = SpvStorageClassPrivate;
+  auto function_storage_class = SpvStorageClassFunction;
+  auto input_storage_class = SpvStorageClassInput;
+
+  // A valid pointer must have the correct |pointee_type_id| and |storageClass|.
+  // A function type pointer with id = 9 and pointee type id 8 should be found.
+  ASSERT_EQ(9, fuzzerutil::MaybeGetPointerType(ir_context, 8,
+                                               function_storage_class));
+  // A function type pointer with id = 15 and pointee type id 6 should be found.
+  ASSERT_EQ(15, fuzzerutil::MaybeGetPointerType(ir_context, 6,
+                                                function_storage_class));
+  // A function type pointer with id = 25 and pointee type id 7 should be found.
+  ASSERT_EQ(25, fuzzerutil::MaybeGetPointerType(ir_context, 7,
+                                                function_storage_class));
+
+  // A private type pointer with id=51 and pointee type id 6 should be found.
+  ASSERT_EQ(51, fuzzerutil::MaybeGetPointerType(ir_context, 6,
+                                                private_storage_class));
+  // A function pointer with id=50 and pointee type id 7 should be found.
+  ASSERT_EQ(50, fuzzerutil::MaybeGetPointerType(ir_context, 7,
+                                                private_storage_class));
+
+  // A input type pointer with id=91 and pointee type id 90 should be found.
+  ASSERT_EQ(
+      91, fuzzerutil::MaybeGetPointerType(ir_context, 90, input_storage_class));
+
+  // A pointer with id=91 and pointee type 90 exisits, but the type should be
+  // input.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetPointerType(ir_context, 90,
+                                               function_storage_class));
+  // A input type pointer with id=91 exists but the pointee id should be 90.
+  ASSERT_EQ(
+      0, fuzzerutil::MaybeGetPointerType(ir_context, 89, input_storage_class));
+  // A input type pointer with pointee id 90 exists but result id of the pointer
+  // should be 91.
+  ASSERT_NE(
+      58, fuzzerutil::MaybeGetPointerType(ir_context, 90, input_storage_class));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetScalarConstantTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %56
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b1"
+               OpName %10 "b2"
+               OpName %12 "b3"
+               OpName %13 "b4"
+               OpName %16 "f1"
+               OpName %18 "f2"
+               OpName %22 "zc"
+               OpName %24 "i1"
+               OpName %28 "i2"
+               OpName %30 "i"
+               OpName %32 "i3"
+               OpName %34 "i4"
+               OpName %39 "f_arr"
+               OpName %49 "i_arr"
+               OpName %56 "value"
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %32 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %49 RelaxedPrecision
+               OpDecorate %56 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 1.23000002
+         %19 = OpConstant %14 1.11000001
+         %20 = OpTypeInt 32 1
+         %21 = OpTypePointer Function %20
+         %23 = OpConstant %20 0
+         %25 = OpConstant %20 1
+         %26 = OpTypeInt 32 0
+         %27 = OpTypePointer Function %26
+         %29 = OpConstant %26 100
+         %31 = OpConstant %26 0
+         %33 = OpConstant %20 -1
+         %35 = OpConstant %20 -99
+         %36 = OpConstant %26 5
+         %37 = OpTypeArray %14 %36
+         %38 = OpTypePointer Function %37
+         %40 = OpConstant %14 5.5
+         %41 = OpConstant %14 4.4000001
+         %42 = OpConstant %14 3.29999995
+         %43 = OpConstant %14 2.20000005
+         %44 = OpConstant %14 1.10000002
+         %45 = OpConstantComposite %37 %40 %41 %42 %43 %44
+         %46 = OpConstant %26 3
+         %47 = OpTypeArray %20 %46
+         %48 = OpTypePointer Function %47
+         %50 = OpConstant %20 3
+         %51 = OpConstant %20 7
+         %52 = OpConstant %20 9
+         %53 = OpConstantComposite %47 %50 %51 %52
+         %55 = OpTypePointer Input %14
+         %56 = OpVariable %55 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %13 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %18 = OpVariable %15 Function
+         %22 = OpVariable %21 Function
+         %24 = OpVariable %21 Function
+         %28 = OpVariable %27 Function
+         %30 = OpVariable %27 Function
+         %32 = OpVariable %21 Function
+         %34 = OpVariable %21 Function
+         %39 = OpVariable %38 Function
+         %49 = OpVariable %48 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %9
+               OpStore %13 %11
+               OpStore %16 %17
+               OpStore %18 %19
+               OpStore %22 %23
+               OpStore %24 %25
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpStore %34 %35
+               OpStore %39 %45
+               OpStore %49 %53
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  opt::IRContext* ir_context = context.get();
+
+  std::vector<uint32_t> uint_words1 = fuzzerutil::IntToWords(100, 32, false);
+  std::vector<uint32_t> uint_words2 = fuzzerutil::IntToWords(0, 32, false);
+  std::vector<uint32_t> int_words1 = fuzzerutil::IntToWords(-99, 32, true);
+  std::vector<uint32_t> int_words2 = fuzzerutil::IntToWords(1, 32, true);
+  uint32_t float_word1 = fuzzerutil::FloatToWord(1.11f);
+  uint32_t float_word2 = fuzzerutil::FloatToWord(4.4f);
+
+  // A unsigned int of value 100 that has a scalar type id of 26 exists and its
+  // id is 29.
+  ASSERT_EQ(
+      29, fuzzerutil::MaybeGetScalarConstant(ir_context, transformation_context,
+                                             uint_words1, 26, false));
+  // A unsigned int of value 0 that has a scalar type id of 26 exists and its id
+  // is 29.
+  ASSERT_EQ(
+      31, fuzzerutil::MaybeGetScalarConstant(ir_context, transformation_context,
+                                             uint_words2, 26, false));
+  // A signed int of value -99 that has a scalar type id of 20 exists and its id
+  // is 35.
+  ASSERT_EQ(35, fuzzerutil::MaybeGetScalarConstant(
+                    ir_context, transformation_context, int_words1, 20, false));
+  // A signed int of value 1 that has a scalar type id of 20 exists and its id
+  // is 25.
+  ASSERT_EQ(25, fuzzerutil::MaybeGetScalarConstant(
+                    ir_context, transformation_context, int_words2, 20, false));
+  // A float of value 1.11 that has a scalar type id of 14 exists and its id
+  // is 19.
+  ASSERT_EQ(19, fuzzerutil::MaybeGetScalarConstant(
+                    ir_context, transformation_context,
+                    std::vector<uint32_t>{float_word1}, 14, false));
+  // A signed int of value 1 that has a scalar type id of 20 exists and its id
+  // is 25.
+  ASSERT_EQ(41, fuzzerutil::MaybeGetScalarConstant(
+                    ir_context, transformation_context,
+                    std::vector<uint32_t>{float_word2}, 14, false));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetStructTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+
+  // 6 and 7 are all valid ids from OpTypeInt and OpTypeFloat
+  // so the result id of 8 should be found.
+  ASSERT_EQ(8, fuzzerutil::MaybeGetStructType(ir_context,
+                                              std::vector<uint32_t>{6, 7}));
+
+  // |component_type_id| of 16 does not exist in the module, so such a struct
+  // type cannot be found.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetStructType(ir_context,
+                                              std::vector<uint32_t>(6, 16)));
+
+  // |component_type_id| of 10 is of OpTypeFunction type and thus the struct
+  // cannot be found.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetStructType(ir_context,
+                                              std::vector<uint32_t>(6, 10)));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetVectorTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+  // The vector type with |element_count| 4 and |component_type_id| 7
+  // is present and has a result id of 90.
+  ASSERT_EQ(90, fuzzerutil::MaybeGetVectorType(ir_context, 7, 4));
+
+  // The vector type with |element_count| 3 and |component_type_id| 7
+  // is not present in the module.
+  ASSERT_EQ(0, fuzzerutil::MaybeGetVectorType(ir_context, 7, 3));
+
+#ifndef NDEBUG
+  // It should abort with |component_type_id| of 100
+  // |component_type_id| must be a valid result id of an OpTypeInt,
+  // OpTypeFloat or OpTypeBool instruction in the module.
+  ASSERT_DEATH(fuzzerutil::MaybeGetVectorType(ir_context, 100, 4),
+               "\\|component_type_id\\| is invalid");
+
+  // It should abort with |element_count| of 5.
+  // |element_count| must be in the range [2,4].
+  ASSERT_DEATH(fuzzerutil::MaybeGetVectorType(ir_context, 7, 5),
+               "Precondition: component count must be in range \\[2, 4\\].");
+#endif
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetVoidTypeTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %92 %52 %53
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpDecorate %92 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+          %8 = OpTypeStruct %6 %7
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %6 %9
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 2
+         %23 = OpConstant %6 1
+         %24 = OpConstant %7 1
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Private %7
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %52 = OpVariable %50 Private
+         %53 = OpVariable %51 Private
+         %80 = OpConstantComposite %8 %21 %24
+         %90 = OpTypeVector %7 4
+         %91 = OpTypePointer Input %90
+         %92 = OpVariable %91 Input
+         %93 = OpConstantComposite %90 %24 %24 %24 %24
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %9 Function
+         %27 = OpVariable %9 Function
+         %22 = OpAccessChain %15 %20 %14
+         %44 = OpCopyObject %9 %20
+         %26 = OpAccessChain %25 %20 %23
+         %29 = OpFunctionCall %6 %12 %27
+         %30 = OpAccessChain %15 %20 %14
+         %45 = OpCopyObject %15 %30
+         %81 = OpCopyObject %9 %27
+         %33 = OpAccessChain %15 %20 %14
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %15 %20 %14
+         %40 = OpAccessChain %15 %20 %14
+         %43 = OpAccessChain %15 %20 %14
+         %82 = OpCopyObject %9 %27
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %6 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %46 = OpCopyObject %9 %11
+         %16 = OpAccessChain %15 %11 %14
+         %95 = OpCopyObject %8 %80
+               OpReturnValue %21
+        %100 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::IRContext* ir_context = context.get();
+  // A void type with a result id of 2 can be found.
+  ASSERT_EQ(2, fuzzerutil::MaybeGetVoidType(ir_context));
+}
+
+TEST(FuzzerutilTest, FuzzerUtilMaybeGetZeroConstantTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %56
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "b1"
+               OpName %10 "b2"
+               OpName %12 "b3"
+               OpName %13 "b4"
+               OpName %16 "f1"
+               OpName %18 "f2"
+               OpName %22 "zc"
+               OpName %24 "i1"
+               OpName %28 "i2"
+               OpName %30 "i"
+               OpName %32 "i3"
+               OpName %34 "i4"
+               OpName %39 "f_arr"
+               OpName %49 "i_arr"
+               OpName %56 "value"
+               OpDecorate %22 RelaxedPrecision
+               OpDecorate %24 RelaxedPrecision
+               OpDecorate %28 RelaxedPrecision
+               OpDecorate %30 RelaxedPrecision
+               OpDecorate %32 RelaxedPrecision
+               OpDecorate %34 RelaxedPrecision
+               OpDecorate %49 RelaxedPrecision
+               OpDecorate %56 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypePointer Function %6
+          %9 = OpConstantTrue %6
+         %11 = OpConstantFalse %6
+         %14 = OpTypeFloat 32
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %14 1.23000002
+         %19 = OpConstant %14 1.11000001
+         %20 = OpTypeInt 32 1
+         %21 = OpTypePointer Function %20
+         %23 = OpConstant %20 0
+         %25 = OpConstant %20 1
+         %26 = OpTypeInt 32 0
+         %27 = OpTypePointer Function %26
+         %29 = OpConstant %26 100
+         %31 = OpConstant %26 0
+         %33 = OpConstant %20 -1
+         %35 = OpConstant %20 -99
+         %36 = OpConstant %26 5
+         %37 = OpTypeArray %14 %36
+         %38 = OpTypePointer Function %37
+         %40 = OpConstant %14 5.5
+         %41 = OpConstant %14 4.4000001
+         %42 = OpConstant %14 3.29999995
+         %43 = OpConstant %14 2.20000005
+         %44 = OpConstant %14 1.10000002
+         %45 = OpConstantComposite %37 %40 %41 %42 %43 %44
+         %46 = OpConstant %26 3
+         %47 = OpTypeArray %20 %46
+         %48 = OpTypePointer Function %47
+         %50 = OpConstant %20 3
+         %51 = OpConstant %20 7
+         %52 = OpConstant %20 9
+         %53 = OpConstantComposite %47 %50 %51 %52
+         %55 = OpTypePointer Input %14
+         %56 = OpVariable %55 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %13 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %18 = OpVariable %15 Function
+         %22 = OpVariable %21 Function
+         %24 = OpVariable %21 Function
+         %28 = OpVariable %27 Function
+         %30 = OpVariable %27 Function
+         %32 = OpVariable %21 Function
+         %34 = OpVariable %21 Function
+         %39 = OpVariable %38 Function
+         %49 = OpVariable %48 Function
+               OpStore %8 %9
+               OpStore %10 %11
+               OpStore %12 %9
+               OpStore %13 %11
+               OpStore %16 %17
+               OpStore %18 %19
+               OpStore %22 %23
+               OpStore %24 %25
+               OpStore %28 %29
+               OpStore %30 %31
+               OpStore %32 %33
+               OpStore %34 %35
+               OpStore %39 %45
+               OpStore %49 %53
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const std::unique_ptr<opt::IRContext> context =
+      BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  opt::IRContext* ir_context = context.get();
+
+  // The id of a boolean constant will be returned give boolean type id 6.
+  uint32_t maybe_bool_id = fuzzerutil::MaybeGetZeroConstant(
+      ir_context, transformation_context, 6, false);
+  // The id of a 32 bit float constant will be returned given the float type
+  // id 14.
+  uint32_t maybe_float_id = fuzzerutil::MaybeGetZeroConstant(
+      ir_context, transformation_context, 14, false);
+  uint32_t maybe_signed_int_id = fuzzerutil::MaybeGetZeroConstant(
+      ir_context, transformation_context, 20, false);
+  uint32_t maybe_unsigned_int_id = fuzzerutil::MaybeGetZeroConstant(
+      ir_context, transformation_context, 26, false);
+
+  // Lists of possible ids for float, signed int, unsigned int and array.
+  std::vector<uint32_t> float_ids{17, 19};
+  std::vector<uint32_t> signed_int_ids{23, 25, 31, 33};
+
+  ASSERT_TRUE(maybe_bool_id == 9 || maybe_bool_id == 11);
+  ASSERT_TRUE(std::find(signed_int_ids.begin(), signed_int_ids.end(),
+                        maybe_signed_int_id) != signed_int_ids.end());
+
+  // There is a unsigned int typed zero constant and its id is 31.
+  ASSERT_EQ(31, maybe_unsigned_int_id);
+
+  // There is no zero float constant.
+  ASSERT_TRUE(std::find(float_ids.begin(), float_ids.end(), maybe_float_id) ==
+              float_ids.end());
+}
+
+TEST(FuzzerutilTest, TypesAreCompatible) {
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpTypeInt 32 0
+          %8 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %8
+         %11 = OpVariable %10 StorageBuffer
+         %86 = OpTypeStruct %9
+         %87 = OpTypePointer Workgroup %86
+         %88 = OpVariable %87 Workgroup
+         %89 = OpTypePointer Workgroup %9
+         %19 = OpConstant %9 0
+         %18 = OpConstant %9 1
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 2
+         %16 = OpConstant %6 7
+         %20 = OpConstant %9 64
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpAccessChain %13 %11 %12
+         %90 = OpAccessChain %89 %88 %19
+         %21 = OpAtomicLoad %6 %14 %15 %20
+         %22 = OpAtomicExchange %6 %14 %15 %20 %16
+         %23 = OpAtomicCompareExchange %6 %14 %15 %20 %12 %16 %15
+         %24 = OpAtomicIIncrement %6 %14 %15 %20
+         %25 = OpAtomicIDecrement %6 %14 %15 %20
+         %26 = OpAtomicIAdd %6  %14 %15 %20 %16
+         %27 = OpAtomicISub %6  %14 %15 %20 %16
+         %28 = OpAtomicSMin %6  %14 %15 %20 %16
+         %29 = OpAtomicUMin %9 %90 %15 %20 %18
+         %30 = OpAtomicSMax %6  %14 %15 %20 %15
+         %31 = OpAtomicUMax %9 %90 %15 %20 %18
+         %32 = OpAtomicAnd  %6  %14 %15 %20 %16
+         %33 = OpAtomicOr   %6  %14 %15 %20 %16
+         %34 = OpAtomicXor  %6  %14 %15 %20 %16
+               OpAtomicStore %14 %15 %20 %16
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  const uint32_t int_type = 6;   // The id of OpTypeInt 32 1
+  const uint32_t uint_type = 9;  // The id of OpTypeInt 32 0
+
+  // OpAtomicLoad
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicLoad, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicLoad, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicLoad, 2,
+                                             int_type, uint_type));
+
+  // OpAtomicExchange
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(
+                   context.get(), SpvOpAtomicExchange, 0, int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicExchange,
+                                             1, int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicExchange,
+                                             2, int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(
+      context.get(), SpvOpAtomicExchange, 3, int_type, uint_type));
+
+  // OpAtomicStore
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore,
+                                              0, int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore,
+                                              3, int_type, uint_type));
+
+  // OpAtomicCompareExchange
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicCompareExchange,
+                                     0, int_type, uint_type),
+      "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(
+      context.get(), SpvOpAtomicCompareExchange, 1, int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(
+      context.get(), SpvOpAtomicCompareExchange, 2, int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(
+      context.get(), SpvOpAtomicCompareExchange, 3, int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(
+      context.get(), SpvOpAtomicCompareExchange, 4, int_type, uint_type));
+
+  // OpAtomicIIncrement
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIIncrement, 0,
+                                     int_type, uint_type),
+      "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(
+      context.get(), SpvOpAtomicIIncrement, 1, int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(
+      context.get(), SpvOpAtomicIIncrement, 2, int_type, uint_type));
+
+// OpAtomicIDecrement
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore,
+                                              0, int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 2,
+                                             int_type, uint_type));
+
+// OpAtomicIAdd
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 3,
+                                              int_type, uint_type));
+
+// OpAtomicISub
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 3,
+                                              int_type, uint_type));
+
+// OpAtomicSMin
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 3,
+                                              int_type, uint_type));
+
+// OpAtomicUMin
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 3,
+                                              int_type, uint_type));
+
+// OpAtomicSMax
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 3,
+                                              int_type, uint_type));
+
+// OpAtomicUMax
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 3,
+                                              int_type, uint_type));
+
+// OpAtomicAnd
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 3,
+                                              int_type, uint_type));
+
+// OpAtomicOr
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 3,
+                                              int_type, uint_type));
+
+// OpAtomicXor
+#ifndef NDEBUG
+  ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 0,
+                                              int_type, uint_type),
+               "Signedness check should not occur on a pointer operand.");
+#endif
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 1,
+                                             int_type, uint_type));
+  ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 2,
+                                             int_type, uint_type));
+  ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 3,
+                                              int_type, uint_type));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/shrinker_test.cpp b/test/fuzz/shrinker_test.cpp
index 42cd182..942de29 100644
--- a/test/fuzz/shrinker_test.cpp
+++ b/test/fuzz/shrinker_test.cpp
@@ -163,15 +163,15 @@
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
       donor_ir_context.get(), validator_options, kConsoleMessageConsumer));
 
-  PseudoRandomGenerator random_generator(0);
-  FuzzerContext fuzzer_context(&random_generator, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   TransformationContext transformation_context(
       MakeUnique<FactManager>(variant_ir_context.get()), validator_options);
 
   protobufs::TransformationSequence transformations;
   FuzzerPassDonateModules pass(variant_ir_context.get(),
                                &transformation_context, &fuzzer_context,
-                               &transformations, {});
+                               &transformations, false, {});
   pass.DonateSingleModule(donor_ir_context.get(), true);
 
   protobufs::FactSequence no_facts;
@@ -341,15 +341,15 @@
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
       donor_ir_context.get(), validator_options, kConsoleMessageConsumer));
 
-  PseudoRandomGenerator random_generator(0);
-  FuzzerContext fuzzer_context(&random_generator, 100);
+  FuzzerContext fuzzer_context(MakeUnique<PseudoRandomGenerator>(0), 100,
+                               false);
   TransformationContext transformation_context(
       MakeUnique<FactManager>(variant_ir_context.get()), validator_options);
 
   protobufs::TransformationSequence transformations;
   FuzzerPassDonateModules pass(variant_ir_context.get(),
                                &transformation_context, &fuzzer_context,
-                               &transformations, {});
+                               &transformations, false, {});
   pass.DonateSingleModule(donor_ir_context.get(), true);
 
   protobufs::FactSequence no_facts;
@@ -365,7 +365,6 @@
           if (inst->opcode() == SpvOpCopyObject) {
             copy_object_count++;
           }
-
         });
     return copy_object_count >= 8;
   };
diff --git a/test/fuzz/transformation_access_chain_test.cpp b/test/fuzz/transformation_access_chain_test.cpp
index 5c43127..bddcf5f 100644
--- a/test/fuzz/transformation_access_chain_test.cpp
+++ b/test/fuzz/transformation_access_chain_test.cpp
@@ -26,6 +26,7 @@
 TEST(TransformationAccessChainTest, BasicTest) {
   std::string shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %48 %54
@@ -63,7 +64,6 @@
          %85 = OpConstant %10 5
          %52 = OpTypeArray %50 %51
          %53 = OpTypePointer Private %52
-         %45 = OpUndef %9
          %46 = OpConstantNull %9
          %47 = OpTypePointer Private %8
          %48 = OpVariable %47 Private
@@ -127,6 +127,16 @@
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
       54);
 
+  // Check the case where the index type is not a 32-bit integer.
+  TransformationAccessChain invalid_index_example1(
+      101, 28, {29}, MakeInstructionDescriptor(42, SpvOpReturn, 0));
+
+  // Since the index  is not a 32-bit integer type but a 32-bit float type,
+  // ValidIndexComposite should return false and thus the transformation is not
+  // applicable.
+  ASSERT_FALSE(invalid_index_example1.IsApplicable(context.get(),
+                                                   transformation_context));
+
   // Bad: id is not fresh
   ASSERT_FALSE(TransformationAccessChain(
                    43, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0))
@@ -194,15 +204,6 @@
 #ifndef NDEBUG
   // Bad: pointer is null
   ASSERT_DEATH(
-      TransformationAccessChain(100, 45, {80},
-                                MakeInstructionDescriptor(24, SpvOpLoad, 0))
-          .IsApplicable(context.get(), transformation_context),
-      "Access chains should not be created from null/undefined pointers");
-#endif
-
-#ifndef NDEBUG
-  // Bad: pointer is undef
-  ASSERT_DEATH(
       TransformationAccessChain(100, 46, {80},
                                 MakeInstructionDescriptor(24, SpvOpLoad, 0))
           .IsApplicable(context.get(), transformation_context),
@@ -304,9 +305,24 @@
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(107));
   }
+  {
+    // Check the case where the access chain's base pointer has the irrelevant
+    // pointee fact; the resulting access chain should inherit this fact.
+    TransformationAccessChain transformation(
+        107, 54, {}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(54));
+  }
 
   std::string after_transformation = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %48 %54
@@ -344,7 +360,6 @@
          %85 = OpConstant %10 5
          %52 = OpTypeArray %50 %51
          %53 = OpTypePointer Private %52
-         %45 = OpUndef %9
          %46 = OpConstantNull %9
          %47 = OpTypePointer Private %8
          %48 = OpVariable %47 Private
@@ -383,6 +398,7 @@
          %23 = OpConvertFToS %10 %22
         %100 = OpAccessChain %70 %43 %80
         %106 = OpAccessChain %11 %14
+        %107 = OpAccessChain %53 %54
          %24 = OpLoad %10 %14
          %25 = OpIAdd %10 %23 %24
                OpReturnValue %25
@@ -391,6 +407,44 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationAccessChainTest, StructIndexMustBeConstant) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %20 = OpUndef %6
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 0
+         %11 = OpConstant %6 2
+         %12 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Bad: %9 is a pointer to a struct, but %20 is not a constant.
+  ASSERT_FALSE(TransformationAccessChain(
+                   100, 9, {20}, MakeInstructionDescriptor(9, SpvOpReturn, 0))
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 TEST(TransformationAccessChainTest, IsomorphicStructs) {
   std::string shader = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp b/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp
index fa8f7bf..d6b2ce5 100644
--- a/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp
+++ b/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp
@@ -937,6 +937,131 @@
       MakeDataDescriptor(166, {}), MakeDataDescriptor(39, {})));
 }
 
+TEST(TransformationAddBitInstructionSynonymTest, DifferentSingedness) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+        %200 = OpTypeInt 32 1
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstant %2 0
+          %6 = OpConstant %2 1
+          %7 = OpConstant %2 2
+          %8 = OpConstant %2 3
+          %9 = OpConstant %2 4
+         %10 = OpConstant %2 5
+         %11 = OpConstant %2 6
+         %12 = OpConstant %2 7
+         %13 = OpConstant %2 8
+         %14 = OpConstant %2 9
+         %15 = OpConstant %2 10
+         %16 = OpConstant %2 11
+         %17 = OpConstant %2 12
+         %18 = OpConstant %2 13
+         %19 = OpConstant %2 14
+         %20 = OpConstant %2 15
+         %21 = OpConstant %2 16
+         %22 = OpConstant %2 17
+         %23 = OpConstant %2 18
+         %24 = OpConstant %2 19
+         %25 = OpConstant %2 20
+         %26 = OpConstant %2 21
+         %27 = OpConstant %2 22
+         %28 = OpConstant %2 23
+         %29 = OpConstant %2 24
+         %30 = OpConstant %2 25
+         %31 = OpConstant %2 26
+         %32 = OpConstant %2 27
+         %33 = OpConstant %2 28
+         %34 = OpConstant %2 29
+         %35 = OpConstant %2 30
+         %36 = OpConstant %2 31
+         %45 = OpConstant %200 32
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+         %39 = OpNot %200 %5 ; bit instruction
+         %40 = OpBitwiseOr %200 %6 %45  ; bit instruction
+         %41 = OpBitwiseAnd %2 %5 %6 ; bit instruction
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Invalid because the sign of id 200 result is not equal to the sign of id 5
+  // operand in OpNot.
+  auto transformation = TransformationAddBitInstructionSynonym(
+      39, {300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312,
+           313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325,
+           326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338,
+           339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351,
+           352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364,
+           365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
+           378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390,
+           391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403,
+           404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416,
+           417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Invalid because the sign of two operands not the same and the first operand
+  // sign not equal the result sign in OpBitwiseOr.
+  transformation = TransformationAddBitInstructionSynonym(
+      40, {300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312,
+           313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325,
+           326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338,
+           339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351,
+           352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364,
+           365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377,
+           378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390,
+           391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403,
+           404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416,
+           417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Successful transformation
+  {
+    // Instruction operands are the same and it's equal with the result sign in
+    // OpBitwiseAnd bitwise operation.
+    transformation = TransformationAddBitInstructionSynonym(
+        41, {46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,
+             59,  60,  61,  62,  63,  64,  65,  66,  67,  68,  69,  70,  71,
+             72,  73,  74,  75,  76,  77,  78,  79,  80,  81,  82,  83,  84,
+             85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,  96,  97,
+             98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+             111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
+             124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136,
+             137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+             150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162,
+             163, 164, 165, 166, 167, 168, 169, 170, 171, 172});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+}
+
 }  // 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
index 3506db6..bd8d91c 100644
--- a/test/fuzz/transformation_add_constant_boolean_test.cpp
+++ b/test/fuzz/transformation_add_constant_boolean_test.cpp
@@ -46,6 +46,7 @@
   spvtools::ValidatorOptions validator_options;
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
+
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
   // True and false can both be added as neither is present.
@@ -68,7 +69,14 @@
   auto add_false = TransformationAddConstantBoolean(8, false, false);
 
   ASSERT_TRUE(add_true.IsApplicable(context.get(), transformation_context));
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(7));
+  ASSERT_EQ(nullptr, context->get_constant_mgr()->FindDeclaredConstant(7));
   ApplyAndCheckFreshIds(add_true, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpConstantTrue, context->get_def_use_mgr()->GetDef(7)->opcode());
+  ASSERT_TRUE(context->get_constant_mgr()
+                  ->FindDeclaredConstant(7)
+                  ->AsBoolConstant()
+                  ->value());
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
 
diff --git a/test/fuzz/transformation_add_constant_composite_test.cpp b/test/fuzz/transformation_add_constant_composite_test.cpp
index 2c296fb..e5cbeec 100644
--- a/test/fuzz/transformation_add_constant_composite_test.cpp
+++ b/test/fuzz/transformation_add_constant_composite_test.cpp
@@ -82,10 +82,30 @@
   ASSERT_FALSE(TransformationAddConstantComposite(100, 39, {11, 12}, false)
                    .IsApplicable(context.get(), transformation_context));
 
-  TransformationAddConstantComposite transformations[] = {
-      // %100 = OpConstantComposite %7 %11 %12
-      TransformationAddConstantComposite(100, 7, {11, 12}, false),
+  {
+    // %100 = OpConstantComposite %7 %11 %12
+    TransformationAddConstantComposite transformation(100, 7, {11, 12}, false);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    ASSERT_EQ(nullptr, context->get_constant_mgr()->FindDeclaredConstant(100));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpConstantComposite,
+              context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_EQ(0.0F, context->get_constant_mgr()
+                        ->FindDeclaredConstant(100)
+                        ->AsVectorConstant()
+                        ->GetComponents()[0]
+                        ->GetFloat());
+    ASSERT_EQ(1.0F, context->get_constant_mgr()
+                        ->FindDeclaredConstant(100)
+                        ->AsVectorConstant()
+                        ->GetComponents()[1]
+                        ->GetFloat());
+  }
 
+  TransformationAddConstantComposite transformations[] = {
       // %101 = OpConstantComposite %7 %14 %15
       TransformationAddConstantComposite(101, 7, {14, 15}, false),
 
diff --git a/test/fuzz/transformation_add_constant_null_test.cpp b/test/fuzz/transformation_add_constant_null_test.cpp
index ce20a67..1553e9f 100644
--- a/test/fuzz/transformation_add_constant_null_test.cpp
+++ b/test/fuzz/transformation_add_constant_null_test.cpp
@@ -78,9 +78,23 @@
   ASSERT_FALSE(TransformationAddConstantNull(100, 22).IsApplicable(
       context.get(), transformation_context));
 
+  {
+    // %100 = OpConstantNull %6
+    TransformationAddConstantNull transformation(100, 6);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    ASSERT_EQ(nullptr, context->get_constant_mgr()->FindDeclaredConstant(100));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpConstantNull,
+              context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_EQ(
+        0.0F,
+        context->get_constant_mgr()->FindDeclaredConstant(100)->GetFloat());
+  }
+
   TransformationAddConstantNull transformations[] = {
-      // %100 = OpConstantNull %6
-      TransformationAddConstantNull(100, 6),
 
       // %101 = OpConstantNull %7
       TransformationAddConstantNull(101, 7),
diff --git a/test/fuzz/transformation_add_constant_scalar_test.cpp b/test/fuzz/transformation_add_constant_scalar_test.cpp
index a153fb1..00c0541 100644
--- a/test/fuzz/transformation_add_constant_scalar_test.cpp
+++ b/test/fuzz/transformation_add_constant_scalar_test.cpp
@@ -114,6 +114,11 @@
   transformation = TransformationAddConstantScalar(19, 5, {0, 1, 2}, false);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests |words| having 2 words for a 32-bit float type.
+  transformation = TransformationAddConstantScalar(19, 4, {0, 1}, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddConstantScalarTest, Apply) {
@@ -171,7 +176,11 @@
       MakeUnique<FactManager>(context.get()), validator_options);
   // Adds 32-bit unsigned integer (1 logical operand with 1 word).
   auto transformation = TransformationAddConstantScalar(19, 2, {4}, false);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(19));
+  ASSERT_EQ(nullptr, context->get_constant_mgr()->FindDeclaredConstant(19));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpConstant, context->get_def_use_mgr()->GetDef(19)->opcode());
+  ASSERT_EQ(4, context->get_constant_mgr()->FindDeclaredConstant(19)->GetU32());
   auto* constant_instruction = context->get_def_use_mgr()->GetDef(19);
   EXPECT_EQ(constant_instruction->NumInOperands(), 1);
   EXPECT_EQ(constant_instruction->NumInOperandWords(), 1);
diff --git a/test/fuzz/transformation_add_copy_memory_test.cpp b/test/fuzz/transformation_add_copy_memory_test.cpp
index 642a556..ff8ac72 100644
--- a/test/fuzz/transformation_add_copy_memory_test.cpp
+++ b/test/fuzz/transformation_add_copy_memory_test.cpp
@@ -26,6 +26,7 @@
 TEST(TransformationAddCopyMemoryTest, BasicTest) {
   std::string shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -64,7 +65,6 @@
          %67 = OpTypePointer Function %66
          %83 = OpTypePointer Private %66
          %86 = OpVariable %79 Private %20
-         %87 = OpUndef %79
          %88 = OpConstantNull %79
           %4 = OpFunction %2 None %3
           %5 = OpLabel
@@ -182,12 +182,6 @@
                                   90, 40, SpvStorageClassPrivate, 0)
           .IsApplicable(context.get(), transformation_context));
 
-  // Source instruction is OpUndef.
-  ASSERT_FALSE(
-      TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
-                                  90, 87, SpvStorageClassPrivate, 0)
-          .IsApplicable(context.get(), transformation_context));
-
   // Source instruction is OpConstantNull.
   ASSERT_FALSE(
       TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
@@ -255,6 +249,7 @@
 
   std::string expected = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -293,7 +288,6 @@
          %67 = OpTypePointer Function %66
          %83 = OpTypePointer Private %66
          %86 = OpVariable %79 Private %20
-         %87 = OpUndef %79
          %88 = OpConstantNull %79
          %90 = OpVariable %79 Private %20
          %92 = OpVariable %78 Private %25
diff --git a/test/fuzz/transformation_add_dead_block_test.cpp b/test/fuzz/transformation_add_dead_block_test.cpp
index 687f00c..3c9e6b4 100644
--- a/test/fuzz/transformation_add_dead_block_test.cpp
+++ b/test/fuzz/transformation_add_dead_block_test.cpp
@@ -142,6 +142,99 @@
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
+TEST(TransformationAddDeadBlockTest, ApplicableWithFalseCondition) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantFalse %2
+
+; main function
+          %6 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %5 %8 %9
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  auto transformation = TransformationAddDeadBlock(14, 11, false);
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantFalse %2
+
+; main function
+          %6 = OpFunction %3 None %4
+          %7 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %5 %8 %9
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %5 %14 %13
+         %14 = OpLabel
+               OpBranch %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
 TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeSelectionMerge) {
   std::string shader = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_global_undef_test.cpp b/test/fuzz/transformation_add_global_undef_test.cpp
index c3a49e4..03b9157 100644
--- a/test/fuzz/transformation_add_global_undef_test.cpp
+++ b/test/fuzz/transformation_add_global_undef_test.cpp
@@ -63,9 +63,18 @@
   ASSERT_FALSE(TransformationAddGlobalUndef(100, 3).IsApplicable(
       context.get(), transformation_context));
 
+  {
+    // %100 = OpUndef %6
+    TransformationAddGlobalUndef transformation(100, 6);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpUndef, context->get_def_use_mgr()->GetDef(100)->opcode());
+  }
+
   TransformationAddGlobalUndef transformations[] = {
-      // %100 = OpUndef %6
-      TransformationAddGlobalUndef(100, 6),
 
       // %101 = OpUndef %7
       TransformationAddGlobalUndef(101, 7),
diff --git a/test/fuzz/transformation_add_global_variable_test.cpp b/test/fuzz/transformation_add_global_variable_test.cpp
index eb958a7..9531ade 100644
--- a/test/fuzz/transformation_add_global_variable_test.cpp
+++ b/test/fuzz/transformation_add_global_variable_test.cpp
@@ -118,11 +118,24 @@
                                                14, false)
                    .IsApplicable(context.get(), transformation_context));
 
-  TransformationAddGlobalVariable transformations[] = {
-      // %100 = OpVariable %12 Private
-      TransformationAddGlobalVariable(100, 12, SpvStorageClassPrivate, 16,
-                                      true),
+  {
+    // %100 = OpVariable %12 Private
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    TransformationAddGlobalVariable transformation(
+        100, 12, SpvStorageClassPrivate, 16, true);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpVariable, context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_EQ(
+        SpvStorageClassPrivate,
+        static_cast<SpvStorageClass>(
+            context->get_def_use_mgr()->GetDef(100)->GetSingleWordInOperand(
+                0)));
+  }
 
+  TransformationAddGlobalVariable transformations[] = {
       // %101 = OpVariable %10 Private
       TransformationAddGlobalVariable(101, 10, SpvStorageClassPrivate, 40,
                                       false),
@@ -225,12 +238,10 @@
           %8 = OpTypeVector %6 2
           %9 = OpTypePointer Function %6
          %10 = OpTypePointer Private %6
-         %20 = OpTypePointer Uniform %6
          %11 = OpTypePointer Function %7
          %12 = OpTypePointer Private %7
          %13 = OpTypePointer Private %8
          %14 = OpVariable %10 Private
-         %15 = OpVariable %20 Uniform
          %16 = OpConstant %7 1
          %17 = OpTypePointer Private %10
          %18 = OpTypeBool
@@ -246,48 +257,96 @@
                OpFunctionEnd
   )";
 
-  const auto env = SPV_ENV_UNIVERSAL_1_4;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  spvtools::ValidatorOptions validator_options;
-  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
-                                               kConsoleMessageConsumer));
-  TransformationContext transformation_context(
-      MakeUnique<FactManager>(context.get()), validator_options);
-  TransformationAddGlobalVariable transformations[] = {
-      // %100 = OpVariable %12 Private
-      TransformationAddGlobalVariable(100, 12, SpvStorageClassPrivate, 16,
-                                      true),
+  for (auto env : {SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_5,
+                   SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_VULKAN_1_2}) {
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    spvtools::ValidatorOptions validator_options;
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
+    TransformationAddGlobalVariable transformations[] = {
+        // %100 = OpVariable %12 Private
+        TransformationAddGlobalVariable(100, 12, SpvStorageClassPrivate, 16,
+                                        true),
 
-      // %101 = OpVariable %12 Private %16
-      TransformationAddGlobalVariable(101, 12, SpvStorageClassPrivate, 16,
-                                      false),
+        // %101 = OpVariable %12 Private %16
+        TransformationAddGlobalVariable(101, 12, SpvStorageClassPrivate, 16,
+                                        false),
 
-      // %102 = OpVariable %19 Private %21
-      TransformationAddGlobalVariable(102, 19, SpvStorageClassPrivate, 21,
-                                      true)};
+        // %102 = OpVariable %19 Private %21
+        TransformationAddGlobalVariable(102, 19, SpvStorageClassPrivate, 21,
+                                        true)};
 
-  for (auto& transformation : transformations) {
+    for (auto& transformation : transformations) {
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
     ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
-  ASSERT_TRUE(
-      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
-  ASSERT_TRUE(
-      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
-  ASSERT_FALSE(
-      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
-  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
-                                               kConsoleMessageConsumer));
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
 
-  std::string after_transformation = R"(
+    std::string after_transformation_enlarged_interface = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %4 "m1" %100 %101 %102
+                 OpEntryPoint Vertex %5 "m2" %100 %101 %102
+                 OpExecutionMode %4 OriginUpperLeft
+                 OpSource ESSL 310
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeInt 32 1
+            %8 = OpTypeVector %6 2
+            %9 = OpTypePointer Function %6
+           %10 = OpTypePointer Private %6
+           %11 = OpTypePointer Function %7
+           %12 = OpTypePointer Private %7
+           %13 = OpTypePointer Private %8
+           %14 = OpVariable %10 Private
+           %16 = OpConstant %7 1
+           %17 = OpTypePointer Private %10
+           %18 = OpTypeBool
+           %19 = OpTypePointer Private %18
+           %21 = OpConstantTrue %18
+          %100 = OpVariable %12 Private %16
+          %101 = OpVariable %12 Private %16
+          %102 = OpVariable %19 Private %21
+            %4 = OpFunction %2 None %3
+           %30 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+            %5 = OpFunction %2 None %3
+           %31 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+    )";
+
+    ASSERT_TRUE(
+        IsEqual(env, after_transformation_enlarged_interface, context.get()));
+  }
+}
+
+TEST(TransformationAddGlobalVariableTest,
+     TestEntryPointInterfaceNoEnlargement) {
+  // This checks that when global variables are added to a SPIR-V 1.3- module,
+  // they are not added to entry points of that module.
+  std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "m1" %100 %101 %102
-               OpEntryPoint Vertex %5 "m2" %100 %101 %102
+               OpEntryPoint Fragment %4 "m1"
+               OpEntryPoint Vertex %5 "m2"
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
           %2 = OpTypeVoid
@@ -297,20 +356,15 @@
           %8 = OpTypeVector %6 2
           %9 = OpTypePointer Function %6
          %10 = OpTypePointer Private %6
-         %20 = OpTypePointer Uniform %6
          %11 = OpTypePointer Function %7
          %12 = OpTypePointer Private %7
          %13 = OpTypePointer Private %8
          %14 = OpVariable %10 Private
-         %15 = OpVariable %20 Uniform
          %16 = OpConstant %7 1
          %17 = OpTypePointer Private %10
          %18 = OpTypeBool
          %19 = OpTypePointer Private %18
          %21 = OpConstantTrue %18
-        %100 = OpVariable %12 Private %16
-        %101 = OpVariable %12 Private %16
-        %102 = OpVariable %19 Private %21
           %4 = OpFunction %2 None %3
          %30 = OpLabel
                OpReturn
@@ -320,7 +374,86 @@
                OpReturn
                OpFunctionEnd
   )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  for (auto env :
+       {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2,
+        SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1}) {
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    spvtools::ValidatorOptions validator_options;
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
+    TransformationAddGlobalVariable transformations[] = {
+        // %100 = OpVariable %12 Private
+        TransformationAddGlobalVariable(100, 12, SpvStorageClassPrivate, 16,
+                                        true),
+
+        // %101 = OpVariable %12 Private %16
+        TransformationAddGlobalVariable(101, 12, SpvStorageClassPrivate, 16,
+                                        false),
+
+        // %102 = OpVariable %19 Private %21
+        TransformationAddGlobalVariable(102, 19, SpvStorageClassPrivate, 21,
+                                        true)};
+
+    for (auto& transformation : transformations) {
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+
+    std::string after_transformation_fixed_interface = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %4 "m1"
+                 OpEntryPoint Vertex %5 "m2"
+                 OpExecutionMode %4 OriginUpperLeft
+                 OpSource ESSL 310
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeInt 32 1
+            %8 = OpTypeVector %6 2
+            %9 = OpTypePointer Function %6
+           %10 = OpTypePointer Private %6
+           %11 = OpTypePointer Function %7
+           %12 = OpTypePointer Private %7
+           %13 = OpTypePointer Private %8
+           %14 = OpVariable %10 Private
+           %16 = OpConstant %7 1
+           %17 = OpTypePointer Private %10
+           %18 = OpTypeBool
+           %19 = OpTypePointer Private %18
+           %21 = OpConstantTrue %18
+          %100 = OpVariable %12 Private %16
+          %101 = OpVariable %12 Private %16
+          %102 = OpVariable %19 Private %21
+            %4 = OpFunction %2 None %3
+           %30 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+            %5 = OpFunction %2 None %3
+           %31 = OpLabel
+                 OpReturn
+                 OpFunctionEnd
+    )";
+
+    ASSERT_TRUE(
+        IsEqual(env, after_transformation_fixed_interface, context.get()));
+  }
 }
 
 TEST(TransformationAddGlobalVariableTest, TestAddingWorkgroupGlobals) {
diff --git a/test/fuzz/transformation_add_local_variable_test.cpp b/test/fuzz/transformation_add_local_variable_test.cpp
index ed57a28..de88573 100644
--- a/test/fuzz/transformation_add_local_variable_test.cpp
+++ b/test/fuzz/transformation_add_local_variable_test.cpp
@@ -98,10 +98,14 @@
   // %105 = OpVariable %50 Function %51
   {
     TransformationAddLocalVariable transformation(105, 50, 4, 51, true);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(105));
+    ASSERT_EQ(nullptr, context->get_instr_block(105));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
                           &transformation_context);
+    ASSERT_EQ(SpvOpVariable, context->get_def_use_mgr()->GetDef(105)->opcode());
+    ASSERT_EQ(5, context->get_instr_block(105)->id());
   }
 
   // %104 = OpVariable %41 Function %46
diff --git a/test/fuzz/transformation_add_parameter_test.cpp b/test/fuzz/transformation_add_parameter_test.cpp
index 7b2a15f..2ae60c9 100644
--- a/test/fuzz/transformation_add_parameter_test.cpp
+++ b/test/fuzz/transformation_add_parameter_test.cpp
@@ -35,6 +35,10 @@
           %7 = OpTypeBool
          %11 = OpTypeInt 32 1
          %16 = OpTypeFloat 32
+         %51 = OpConstant %11 2
+         %52 = OpTypeArray %16 %51
+         %53 = OpConstant %16 7
+         %54 = OpConstantComposite %52 %53 %53
           %3 = OpTypeFunction %2
           %6 = OpTypeFunction %7 %7
           %8 = OpConstant %11 23
@@ -141,6 +145,14 @@
     ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(60));
   }
   {
+    TransformationAddParameter correct(9, 68, 52, {{{13, 54}}}, 69);
+    ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(correct, context.get(), &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(68));
+  }
+  {
     TransformationAddParameter correct(17, 62, 7, {{}}, 63);
     ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(correct, context.get(), &transformation_context);
@@ -177,6 +189,10 @@
           %7 = OpTypeBool
          %11 = OpTypeInt 32 1
          %16 = OpTypeFloat 32
+         %51 = OpConstant %11 2
+         %52 = OpTypeArray %16 %51
+         %53 = OpConstant %16 7
+         %54 = OpConstantComposite %52 %53 %53
           %3 = OpTypeFunction %2
           %8 = OpConstant %11 23
          %12 = OpConstantTrue %7
@@ -188,11 +204,11 @@
          %41 = OpTypeStruct %11 %16
          %42 = OpConstantComposite %41 %8 %32
          %44 = OpTypeFunction %2 %41 %7
-          %6 = OpTypeFunction %7 %7 %11
+          %6 = OpTypeFunction %7 %7 %11 %52
          %65 = OpTypeFunction %2 %31
           %4 = OpFunction %2 None %3
           %5 = OpLabel
-         %13 = OpFunctionCall %7 %9 %12 %8
+         %13 = OpFunctionCall %7 %9 %12 %8 %54
                OpReturn
                OpFunctionEnd
 
@@ -200,6 +216,7 @@
           %9 = OpFunction %7 None %6
          %14 = OpFunctionParameter %7
          %60 = OpFunctionParameter %11
+         %68 = OpFunctionParameter %52
          %10 = OpLabel
                OpReturnValue %12
                OpFunctionEnd
diff --git a/test/fuzz/transformation_add_synonym_test.cpp b/test/fuzz/transformation_add_synonym_test.cpp
index 3803fa3..ffcf1c9 100644
--- a/test/fuzz/transformation_add_synonym_test.cpp
+++ b/test/fuzz/transformation_add_synonym_test.cpp
@@ -197,6 +197,7 @@
          %37 = OpTypeVector %36 2
          %38 = OpConstantTrue %36
          %39 = OpConstantComposite %37 %38 %38
+         %40 = OpConstant %6 37
           %4 = OpFunction %2 None %3
           %5 = OpLabel
                OpReturn
@@ -249,6 +250,29 @@
       ++fresh_id;
     }
   }
+  {
+    TransformationAddSynonym transformation(
+        40, protobufs::TransformationAddSynonym::BITWISE_OR, fresh_id,
+        insert_before);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(40, {}), MakeDataDescriptor(fresh_id, {})));
+    ++fresh_id;
+  }
+  {
+    TransformationAddSynonym transformation(
+        40, protobufs::TransformationAddSynonym::BITWISE_XOR, fresh_id,
+        insert_before);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(40, {}), MakeDataDescriptor(fresh_id, {})));
+  }
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -289,6 +313,7 @@
          %37 = OpTypeVector %36 2
          %38 = OpConstantTrue %36
          %39 = OpConstantComposite %37 %38 %38
+         %40 = OpConstant %6 37
           %4 = OpFunction %2 None %3
           %5 = OpLabel
          %50 = OpIAdd %6 %9 %7
@@ -303,6 +328,8 @@
          %59 = OpFMul %14 %17 %16
          %60 = OpFMul %18 %23 %20
          %61 = OpIMul %24 %29 %26
+         %62 = OpBitwiseOr %6 %40 %7
+         %63 = OpBitwiseXor %6 %40 %7
                OpReturn
                OpFunctionEnd
   )";
@@ -1215,9 +1242,10 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
-TEST(TransformationAddSynonymTest, DoNotCopyNullOrUndefPointers) {
+TEST(TransformationAddSynonymTest, DoNotCopyNullPointers) {
   std::string shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -1228,7 +1256,6 @@
           %6 = OpTypeInt 32 1
           %7 = OpTypePointer Function %6
           %8 = OpConstantNull %7
-          %9 = OpUndef %7
           %4 = OpFunction %2 None %3
           %5 = OpLabel
                OpReturn
@@ -1248,12 +1275,6 @@
                    8, protobufs::TransformationAddSynonym::COPY_OBJECT, 100,
                    MakeInstructionDescriptor(5, SpvOpReturn, 0))
                    .IsApplicable(context.get(), transformation_context));
-
-  // Illegal to copy an OpUndef of pointer type.
-  ASSERT_FALSE(TransformationAddSynonym(
-                   9, protobufs::TransformationAddSynonym::COPY_OBJECT, 100,
-                   MakeInstructionDescriptor(5, SpvOpReturn, 0))
-                   .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationAddSynonymTest, PropagateIrrelevantPointeeFact) {
diff --git a/test/fuzz/transformation_add_type_array_test.cpp b/test/fuzz/transformation_add_type_array_test.cpp
index ab4ed9a..2ef8200 100644
--- a/test/fuzz/transformation_add_type_array_test.cpp
+++ b/test/fuzz/transformation_add_type_array_test.cpp
@@ -90,19 +90,34 @@
   ASSERT_FALSE(TransformationAddTypeArray(100, 11, 17)
                    .IsApplicable(context.get(), transformation_context));
 
-  TransformationAddTypeArray transformations[] = {
-      // %100 = OpTypeArray %10 %16
-      TransformationAddTypeArray(100, 10, 16),
-
-      // %101 = OpTypeArray %7 %12
-      TransformationAddTypeArray(101, 7, 12)};
-
-  for (auto& transformation : transformations) {
+  {
+    // %100 = OpTypeArray %10 %16
+    TransformationAddTypeArray transformation(100, 10, 16);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(100));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
                           &transformation_context);
+    ASSERT_EQ(SpvOpTypeArray,
+              context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_NE(nullptr, context->get_type_mgr()->GetType(100)->AsArray());
   }
+
+  {
+    // %101 = OpTypeArray %7 %12
+    TransformationAddTypeArray transformation(101, 7, 12);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(101));
+    ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(101));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpTypeArray,
+              context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_NE(nullptr, context->get_type_mgr()->GetType(100)->AsArray());
+  }
+
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
 
diff --git a/test/fuzz/transformation_add_type_boolean_test.cpp b/test/fuzz/transformation_add_type_boolean_test.cpp
index 88d9f5b..a8e657b 100644
--- a/test/fuzz/transformation_add_type_boolean_test.cpp
+++ b/test/fuzz/transformation_add_type_boolean_test.cpp
@@ -52,9 +52,13 @@
       context.get(), transformation_context));
 
   auto add_type_bool = TransformationAddTypeBoolean(100);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+  ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(100));
   ASSERT_TRUE(
       add_type_bool.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(add_type_bool, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpTypeBool, context->get_def_use_mgr()->GetDef(100)->opcode());
+  ASSERT_NE(nullptr, context->get_type_mgr()->GetType(100)->AsBool());
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
 
diff --git a/test/fuzz/transformation_add_type_float_test.cpp b/test/fuzz/transformation_add_type_float_test.cpp
index 235d61b..9275bec 100644
--- a/test/fuzz/transformation_add_type_float_test.cpp
+++ b/test/fuzz/transformation_add_type_float_test.cpp
@@ -61,7 +61,8 @@
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
 
-  // Tests existing 16-bit float type.
+  // The transformation is not applicable because there is already a 16-bit
+  // float type declared in the module.
   transformation = TransformationAddTypeFloat(7, 16);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
@@ -70,6 +71,19 @@
   transformation = TransformationAddTypeFloat(7, 32);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
+
+  // By default, SPIR-V does not support 64-bit float types.
+  // Below we add such capability, so the test should now pass.
+  context.get()->get_feature_mgr()->AddCapability(SpvCapabilityFloat64);
+  ASSERT_TRUE(TransformationAddTypeFloat(7, 64).IsApplicable(
+      context.get(), transformation_context));
+
+#ifndef NDEBUG
+  // Should not be able to add float type of width different from 16/32/64
+  ASSERT_DEATH(TransformationAddTypeFloat(7, 20).IsApplicable(
+                   context.get(), transformation_context),
+               "Unexpected float type width");
+#endif
 }
 
 TEST(TransformationAddTypeFloatTest, Apply) {
@@ -103,15 +117,27 @@
       MakeUnique<FactManager>(context.get()), validator_options);
   // Adds 16-bit float type.
   auto transformation = TransformationAddTypeFloat(6, 16);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(6));
+  ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(6));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpTypeFloat, context->get_def_use_mgr()->GetDef(6)->opcode());
+  ASSERT_NE(nullptr, context->get_type_mgr()->GetType(6)->AsFloat());
 
   // Adds 32-bit float type.
   transformation = TransformationAddTypeFloat(7, 32);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(7));
+  ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(7));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpTypeFloat, context->get_def_use_mgr()->GetDef(7)->opcode());
+  ASSERT_NE(nullptr, context->get_type_mgr()->GetType(7)->AsFloat());
 
   // Adds 64-bit float type.
   transformation = TransformationAddTypeFloat(8, 64);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(8));
+  ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(8));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpTypeFloat, context->get_def_use_mgr()->GetDef(8)->opcode());
+  ASSERT_NE(nullptr, context->get_type_mgr()->GetType(8)->AsFloat());
 
   std::string variant_shader = R"(
          OpCapability Shader
diff --git a/test/fuzz/transformation_add_type_int_test.cpp b/test/fuzz/transformation_add_type_int_test.cpp
index ee4e799..ed8e00a 100644
--- a/test/fuzz/transformation_add_type_int_test.cpp
+++ b/test/fuzz/transformation_add_type_int_test.cpp
@@ -85,6 +85,29 @@
   transformation = TransformationAddTypeInt(7, 32, true);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
+
+  // By default SPIR-V does not support 16-bit integers.
+  // Below we add such capability, so the test should now be succesful.
+  context.get()->get_feature_mgr()->AddCapability(SpvCapabilityInt16);
+  ASSERT_TRUE(TransformationAddTypeInt(7, 16, true)
+                  .IsApplicable(context.get(), transformation_context));
+
+  // By default SPIR-V does not support 64-bit integers.
+  // Below we add such capability, so the test should now pass.
+  context.get()->get_feature_mgr()->AddCapability(SpvCapabilityInt64);
+  ASSERT_TRUE(TransformationAddTypeInt(7, 64, true)
+                  .IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  // Should not be able to add signed/unsigned integers of width different from
+  // 16/32/64 bits.
+  ASSERT_DEATH(TransformationAddTypeInt(7, 20, false)
+                   .IsApplicable(context.get(), transformation_context),
+               "Unexpected integer type width");
+  ASSERT_DEATH(TransformationAddTypeInt(12, 15, false)
+                   .IsApplicable(context.get(), transformation_context),
+               "Unexpected integer type width");
+#endif
 }
 
 TEST(TransformationAddTypeIntTest, Apply) {
@@ -118,8 +141,14 @@
   TransformationContext transformation_context(
       MakeUnique<FactManager>(context.get()), validator_options);
   // Adds signed 8-bit integer type.
+  // For this transformation we also check that the def-use manager and type
+  // manager are updated appropriately.
   auto transformation = TransformationAddTypeInt(6, 8, true);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(6));
+  ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(6));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpTypeInt, context->get_def_use_mgr()->GetDef(6)->opcode());
+  ASSERT_NE(nullptr, context->get_type_mgr()->GetType(6)->AsInteger());
 
   // Adds signed 16-bit integer type.
   transformation = TransformationAddTypeInt(7, 16, true);
diff --git a/test/fuzz/transformation_add_type_matrix_test.cpp b/test/fuzz/transformation_add_type_matrix_test.cpp
index 926e983..df0111e 100644
--- a/test/fuzz/transformation_add_type_matrix_test.cpp
+++ b/test/fuzz/transformation_add_type_matrix_test.cpp
@@ -63,10 +63,21 @@
   ASSERT_FALSE(TransformationAddTypeMatrix(100, 11, 2)
                    .IsApplicable(context.get(), transformation_context));
 
-  TransformationAddTypeMatrix transformations[] = {
-      // %100 = OpTypeMatrix %8 2
-      TransformationAddTypeMatrix(100, 8, 2),
+  {
+    // %100 = OpTypeMatrix %8 2
+    TransformationAddTypeMatrix transformation(100, 8, 2);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(100));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpTypeMatrix,
+              context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_NE(nullptr, context->get_type_mgr()->GetType(100)->AsMatrix());
+  }
 
+  TransformationAddTypeMatrix transformations[] = {
       // %101 = OpTypeMatrix %8 3
       TransformationAddTypeMatrix(101, 8, 3),
 
diff --git a/test/fuzz/transformation_add_type_pointer_test.cpp b/test/fuzz/transformation_add_type_pointer_test.cpp
index 985e904..b9072e3 100644
--- a/test/fuzz/transformation_add_type_pointer_test.cpp
+++ b/test/fuzz/transformation_add_type_pointer_test.cpp
@@ -133,10 +133,24 @@
   ASSERT_FALSE(bad_result_id_is_not_fresh.IsApplicable(context.get(),
                                                        transformation_context));
 
+  {
+    auto& transformation = good_new_private_pointer_to_t;
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(101));
+    ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(101));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    ASSERT_EQ(SpvOpTypePointer,
+              context->get_def_use_mgr()->GetDef(101)->opcode());
+    ASSERT_NE(nullptr, context->get_type_mgr()->GetType(101)->AsPointer());
+  }
+
   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_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}) {
diff --git a/test/fuzz/transformation_add_type_struct_test.cpp b/test/fuzz/transformation_add_type_struct_test.cpp
index b57bab2..7fb91ab 100644
--- a/test/fuzz/transformation_add_type_struct_test.cpp
+++ b/test/fuzz/transformation_add_type_struct_test.cpp
@@ -63,10 +63,21 @@
   ASSERT_FALSE(TransformationAddTypeStruct(100, {3}).IsApplicable(
       context.get(), transformation_context));
 
-  TransformationAddTypeStruct transformations[] = {
-      // %100 = OpTypeStruct %6 %7 %8 %9 %10 %11
-      TransformationAddTypeStruct(100, {6, 7, 8, 9, 10, 11}),
+  {
+    // %100 = OpTypeStruct %6 %7 %8 %9 %10 %11
+    TransformationAddTypeStruct transformation(100, {6, 7, 8, 9, 10, 11});
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(100));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpTypeStruct,
+              context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_NE(nullptr, context->get_type_mgr()->GetType(100)->AsStruct());
+  }
 
+  TransformationAddTypeStruct transformations[] = {
       // %101 = OpTypeStruct
       TransformationAddTypeStruct(101, {}),
 
diff --git a/test/fuzz/transformation_add_type_vector_test.cpp b/test/fuzz/transformation_add_type_vector_test.cpp
index a49ba6e..755bc4a 100644
--- a/test/fuzz/transformation_add_type_vector_test.cpp
+++ b/test/fuzz/transformation_add_type_vector_test.cpp
@@ -57,10 +57,21 @@
   ASSERT_FALSE(TransformationAddTypeVector(100, 1, 2).IsApplicable(
       context.get(), transformation_context));
 
-  TransformationAddTypeVector transformations[] = {
-      // %100 = OpTypeVector %6 2
-      TransformationAddTypeVector(100, 6, 2),
+  {
+    // %100 = OpTypeVector %6 2
+    TransformationAddTypeVector transformation(100, 6, 2);
+    ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(100));
+    ASSERT_EQ(nullptr, context->get_type_mgr()->GetType(100));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_EQ(SpvOpTypeVector,
+              context->get_def_use_mgr()->GetDef(100)->opcode());
+    ASSERT_NE(nullptr, context->get_type_mgr()->GetType(100)->AsVector());
+  }
 
+  TransformationAddTypeVector transformations[] = {
       // %101 = OpTypeVector %7 3
       TransformationAddTypeVector(101, 7, 3),
 
diff --git a/test/fuzz/transformation_composite_construct_test.cpp b/test/fuzz/transformation_composite_construct_test.cpp
index edbfe3b..3c5f731 100644
--- a/test/fuzz/transformation_composite_construct_test.cpp
+++ b/test/fuzz/transformation_composite_construct_test.cpp
@@ -142,12 +142,29 @@
   TransformationCompositeConstruct make_vec2_array_length_3_bad(
       37, {41, 45, 27, 27}, MakeInstructionDescriptor(46, SpvOpAccessChain, 0),
       200);
+  // The first component does not correspond to an instruction with a result
+  // type so this check should return false.
+  TransformationCompositeConstruct make_vec2_array_length_3_nores(
+      37, {2, 45, 27}, MakeInstructionDescriptor(46, SpvOpAccessChain, 0), 200);
   ASSERT_TRUE(make_vec2_array_length_3.IsApplicable(context.get(),
                                                     transformation_context));
   ASSERT_FALSE(make_vec2_array_length_3_bad.IsApplicable(
       context.get(), transformation_context));
+  ASSERT_FALSE(make_vec2_array_length_3_nores.IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(200));
+  ASSERT_EQ(nullptr, context->get_instr_block(200));
+  uint32_t num_uses_of_41_before = context->get_def_use_mgr()->NumUses(41);
+  uint32_t num_uses_of_45_before = context->get_def_use_mgr()->NumUses(45);
+  uint32_t num_uses_of_27_before = context->get_def_use_mgr()->NumUses(27);
   ApplyAndCheckFreshIds(make_vec2_array_length_3, context.get(),
                         &transformation_context);
+  ASSERT_EQ(SpvOpCompositeConstruct,
+            context->get_def_use_mgr()->GetDef(200)->opcode());
+  ASSERT_EQ(34, context->get_instr_block(200)->id());
+  ASSERT_EQ(num_uses_of_41_before + 1, context->get_def_use_mgr()->NumUses(41));
+  ASSERT_EQ(num_uses_of_45_before + 1, context->get_def_use_mgr()->NumUses(45));
+  ASSERT_EQ(num_uses_of_27_before + 1, context->get_def_use_mgr()->NumUses(27));
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -412,9 +429,15 @@
   // Bad: %35 is mat4x3, not mat3x4.
   TransformationCompositeConstruct make_mat34_bad(
       35, {25, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200);
+  // The first component does not correspond to an instruction with a result
+  // type so this check should return false.
+  TransformationCompositeConstruct make_mat34_nores(
+      32, {2, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200);
   ASSERT_TRUE(make_mat34.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_mat34_bad.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      make_mat34_nores.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(make_mat34, context.get(), &transformation_context);
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
@@ -625,9 +648,15 @@
   // Bad: Too few fields to make the struct.
   TransformationCompositeConstruct make_inner_bad(
       9, {25}, MakeInstructionDescriptor(57, SpvOpAccessChain, 0), 200);
+  // The first component does not correspond to an instruction with a result
+  // type so this check should return false.
+  TransformationCompositeConstruct make_inner_nores(
+      9, {2, 19}, MakeInstructionDescriptor(57, SpvOpAccessChain, 0), 200);
   ASSERT_TRUE(make_inner.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_inner_bad.IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      make_inner_nores.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(make_inner, context.get(), &transformation_context);
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
diff --git a/test/fuzz/transformation_composite_extract_test.cpp b/test/fuzz/transformation_composite_extract_test.cpp
index 383a4db..1df5591 100644
--- a/test/fuzz/transformation_composite_extract_test.cpp
+++ b/test/fuzz/transformation_composite_extract_test.cpp
@@ -143,10 +143,18 @@
 
   TransformationCompositeExtract transformation_1(
       MakeInstructionDescriptor(36, SpvOpConvertFToS, 0), 201, 100, {2});
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(201));
+  ASSERT_EQ(nullptr, context->get_instr_block(201));
+  uint32_t num_uses_of_100_before = context->get_def_use_mgr()->NumUses(100);
   ASSERT_TRUE(
       transformation_1.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation_1, context.get(),
                         &transformation_context);
+  ASSERT_EQ(SpvOpCompositeExtract,
+            context->get_def_use_mgr()->GetDef(201)->opcode());
+  ASSERT_EQ(15, context->get_instr_block(201)->id());
+  ASSERT_EQ(num_uses_of_100_before + 1,
+            context->get_def_use_mgr()->NumUses(100));
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
 
diff --git a/test/fuzz/transformation_equation_instruction_test.cpp b/test/fuzz/transformation_equation_instruction_test.cpp
index 654fffc..5b5033d 100644
--- a/test/fuzz/transformation_equation_instruction_test.cpp
+++ b/test/fuzz/transformation_equation_instruction_test.cpp
@@ -102,8 +102,12 @@
       14, SpvOpSNegate, {7}, return_instruction);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(14));
+  ASSERT_EQ(nullptr, context->get_instr_block(14));
   ApplyAndCheckFreshIds(transformation1, context.get(),
                         &transformation_context);
+  ASSERT_EQ(SpvOpSNegate, context->get_def_use_mgr()->GetDef(14)->opcode());
+  ASSERT_EQ(13, context->get_instr_block(14)->id());
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
 
diff --git a/test/fuzz/transformation_flatten_conditional_branch_test.cpp b/test/fuzz/transformation_flatten_conditional_branch_test.cpp
index 1a0ff6a..800af0e 100644
--- a/test/fuzz/transformation_flatten_conditional_branch_test.cpp
+++ b/test/fuzz/transformation_flatten_conditional_branch_test.cpp
@@ -1334,20 +1334,24 @@
                OpFunctionEnd
   )";
 
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  spvtools::ValidatorOptions validator_options;
-  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
-                                               kConsoleMessageConsumer));
+  for (auto env :
+       {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2,
+        SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1}) {
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    spvtools::ValidatorOptions validator_options;
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
 
-  TransformationContext transformation_context(
-      MakeUnique<FactManager>(context.get()), validator_options);
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
 
-  auto transformation =
-      TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
-  ASSERT_FALSE(
-      transformation.IsApplicable(context.get(), transformation_context));
+    auto transformation =
+        TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
+    ASSERT_FALSE(
+        transformation.IsApplicable(context.get(), transformation_context));
+  }
 }
 
 TEST(TransformationFlattenConditionalBranchTest,
diff --git a/test/fuzz/transformation_load_test.cpp b/test/fuzz/transformation_load_test.cpp
index a03ffdd..370826e 100644
--- a/test/fuzz/transformation_load_test.cpp
+++ b/test/fuzz/transformation_load_test.cpp
@@ -26,6 +26,7 @@
 TEST(TransformationLoadTest, BasicTest) {
   std::string shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -49,7 +50,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
           %4 = OpFunction %2 None %3
@@ -129,65 +129,69 @@
 
   // Pointers that cannot be used:
   //  60 - null
-  //  61 - undefined
 
   // Bad: id is not fresh
-  ASSERT_FALSE(TransformationLoad(
-                   33, 33, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationLoad(33, 33, false, 0, 0,
+                         MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
   // Bad: attempt to load from 11 from outside its function
-  ASSERT_FALSE(TransformationLoad(
-                   100, 11, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationLoad(100, 11, false, 0, 0,
+                         MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: pointer is not available
-  ASSERT_FALSE(TransformationLoad(
-                   100, 33, MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationLoad(100, 33, false, 0, 0,
+                         MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: attempt to insert before OpVariable
-  ASSERT_FALSE(TransformationLoad(
-                   100, 27, MakeInstructionDescriptor(27, SpvOpVariable, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationLoad(100, 27, false, 0, 0,
+                         MakeInstructionDescriptor(27, SpvOpVariable, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: pointer id does not exist
   ASSERT_FALSE(
-      TransformationLoad(100, 1000,
+      TransformationLoad(100, 1000, false, 0, 0,
                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
           .IsApplicable(context.get(), transformation_context));
 
   // Bad: pointer id exists but does not have a type
-  ASSERT_FALSE(TransformationLoad(
-                   100, 5, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Bad: pointer id exists and has a type, but is not a pointer
-  ASSERT_FALSE(TransformationLoad(
-                   100, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Bad: attempt to load from null pointer
-  ASSERT_FALSE(TransformationLoad(
-                   100, 60, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Bad: attempt to load from undefined pointer
-  ASSERT_FALSE(TransformationLoad(
-                   100, 61, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
-  // Bad: %40 is not available at the program point
   ASSERT_FALSE(
-      TransformationLoad(100, 40, MakeInstructionDescriptor(37, SpvOpReturn, 0))
+      TransformationLoad(100, 5, false, 0, 0,
+                         MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
           .IsApplicable(context.get(), transformation_context));
 
-  // Bad: The described instruction does not exist
-  ASSERT_FALSE(TransformationLoad(
-                   100, 33, MakeInstructionDescriptor(1000, SpvOpReturn, 0))
+  // Bad: pointer id exists and has a type, but is not a pointer
+  ASSERT_FALSE(
+      TransformationLoad(100, 24, false, 0, 0,
+                         MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: attempt to load from null pointer
+  ASSERT_FALSE(
+      TransformationLoad(100, 60, false, 0, 0,
+                         MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: %40 is not available at the program point
+  ASSERT_FALSE(TransformationLoad(100, 40, false, 0, 0,
+                                  MakeInstructionDescriptor(37, SpvOpReturn, 0))
                    .IsApplicable(context.get(), transformation_context));
 
+  // Bad: The described instruction does not exist
+  ASSERT_FALSE(
+      TransformationLoad(100, 33, false, 0, 0,
+                         MakeInstructionDescriptor(1000, SpvOpReturn, 0))
+          .IsApplicable(context.get(), transformation_context));
+
   {
     TransformationLoad transformation(
-        100, 33, MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
+        100, 33, false, 0, 0,
+        MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -198,7 +202,8 @@
 
   {
     TransformationLoad transformation(
-        101, 46, MakeInstructionDescriptor(16, SpvOpReturnValue, 0));
+        101, 46, false, 0, 0,
+        MakeInstructionDescriptor(16, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -209,7 +214,8 @@
 
   {
     TransformationLoad transformation(
-        102, 16, MakeInstructionDescriptor(16, SpvOpReturnValue, 0));
+        102, 16, false, 0, 0,
+        MakeInstructionDescriptor(16, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -220,7 +226,8 @@
 
   {
     TransformationLoad transformation(
-        103, 40, MakeInstructionDescriptor(43, SpvOpAccessChain, 0));
+        103, 40, false, 0, 0,
+        MakeInstructionDescriptor(43, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -231,6 +238,7 @@
 
   std::string after_transformation = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -254,7 +262,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
           %4 = OpFunction %2 None %3
@@ -293,6 +300,296 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationLoadTest, AtomicLoadTestCase) {
+  const std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int8
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 8 1
+          %9 = OpTypeInt 32 0
+         %26 = OpTypeFloat 32
+          %8 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %8
+         %11 = OpVariable %10 StorageBuffer
+         %19 = OpConstant %26 0
+         %18 = OpConstant %9 1
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 4
+         %16 = OpConstant %6 7
+         %17 = OpConstant %7 4
+         %20 = OpConstant %9 64
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpAccessChain %13 %11 %12
+         %24 = OpAccessChain %13 %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Bad: id is not fresh.
+  ASSERT_FALSE(
+      TransformationLoad(14, 14, true, 15, 20,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: id 100 of memory scope instruction does not exist.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 100, 20,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: id 100 of memory semantics instruction does not exist.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 15, 100,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: memory scope should be |OpConstant| opcode.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 5, 20,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: memory semantics should be |OpConstant| opcode.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 15, 5,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: The memory scope instruction must have an Integer operand.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 15, 19,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: The memory memory semantics instruction must have an Integer operand.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 19, 20,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Integer size of the memory scope must be equal to 32 bits.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 17, 20,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Integer size of memory semantics must be equal to 32 bits.
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 15, 17,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: memory scope value must be 4 (SpvScopeInvocation).
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 16, 20,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: memory semantics value must be either:
+  // 64 (SpvMemorySemanticsUniformMemoryMask)
+  // 256 (SpvMemorySemanticsWorkgroupMemoryMask)
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, true, 15, 16,
+                         MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: The described instruction does not exist
+  ASSERT_FALSE(
+      TransformationLoad(21, 14, false, 15, 20,
+                         MakeInstructionDescriptor(150, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Successful transformations.
+  {
+    TransformationLoad transformation(
+        21, 14, true, 15, 20,
+        MakeInstructionDescriptor(24, SpvOpAccessChain, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+               OpCapability Int8
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 8 1
+          %9 = OpTypeInt 32 0
+         %26 = OpTypeFloat 32
+          %8 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %8
+         %11 = OpVariable %10 StorageBuffer
+         %19 = OpConstant %26 0
+         %18 = OpConstant %9 1
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 4
+         %16 = OpConstant %6 7
+         %17 = OpConstant %7 4
+         %20 = OpConstant %9 64
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpAccessChain %13 %11 %12
+         %21 = OpAtomicLoad %6 %14 %15 %20
+         %24 = OpAccessChain %13 %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationLoadTest, AtomicLoadTestCaseForWorkgroupMemory) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int8
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %26 = OpTypeFloat 32
+          %27 = OpTypeInt 8 1
+          %7 = OpTypeInt 32 0 ; 0 means unsigned
+          %8 = OpConstant %7 0
+         %17 = OpConstant %27 4
+         %19 = OpConstant %26 0
+          %9 = OpTypePointer Function %6
+         %13 = OpTypeStruct %6
+         %12 = OpTypePointer Workgroup %13
+         %11 = OpVariable %12 Workgroup
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 4
+         %23 = OpConstant %6 256
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Workgroup %6
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %53 = OpVariable %51 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %50 %11 %14
+         %40 = OpAccessChain %50 %11 %14
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Bad: Can't insert OpAccessChain before the id 23 of memory scope.
+  ASSERT_FALSE(
+      TransformationLoad(60, 38, true, 21, 23,
+                         MakeInstructionDescriptor(23, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Can't insert OpAccessChain before the id 23 of memory semantics.
+  ASSERT_FALSE(
+      TransformationLoad(60, 38, true, 21, 23,
+                         MakeInstructionDescriptor(21, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Successful transformations.
+  {
+    TransformationLoad transformation(
+        60, 38, true, 21, 23,
+        MakeInstructionDescriptor(40, SpvOpAccessChain, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+               OpCapability Int8
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %26 = OpTypeFloat 32
+          %27 = OpTypeInt 8 1
+          %7 = OpTypeInt 32 0 ; 0 means unsigned
+          %8 = OpConstant %7 0
+         %17 = OpConstant %27 4
+         %19 = OpConstant %26 0
+          %9 = OpTypePointer Function %6
+         %13 = OpTypeStruct %6
+         %12 = OpTypePointer Workgroup %13
+         %11 = OpVariable %12 Workgroup
+         %14 = OpConstant %6 0
+         %15 = OpTypePointer Function %6
+         %51 = OpTypePointer Private %6
+         %21 = OpConstant %6 4
+         %23 = OpConstant %6 256
+         %25 = OpTypePointer Function %7
+         %50 = OpTypePointer Workgroup %6
+         %34 = OpTypeBool
+         %35 = OpConstantFalse %34
+         %53 = OpVariable %51 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %37 None
+               OpBranchConditional %35 %36 %37
+         %36 = OpLabel
+         %38 = OpAccessChain %50 %11 %14
+         %60 = OpAtomicLoad %6 %38 %21 %23
+         %40 = OpAccessChain %50 %11 %14
+               OpBranch %37
+         %37 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_merge_function_returns_test.cpp b/test/fuzz/transformation_merge_function_returns_test.cpp
index e60d345..400d49a 100644
--- a/test/fuzz/transformation_merge_function_returns_test.cpp
+++ b/test/fuzz/transformation_merge_function_returns_test.cpp
@@ -147,12 +147,12 @@
       MakeUnique<FactManager>(context.get()), validator_options);
 
   // Function %1 does not exist.
-  ASSERT_FALSE(TransformationMergeFunctionReturns(1, 100, 101, 0, 0, {{}})
+  ASSERT_FALSE(TransformationMergeFunctionReturns(1, 100, 200, 101, 0, 0, {{}})
                    .IsApplicable(context.get(), transformation_context));
 
   // The entry block (%22) of function %15 does not branch unconditionally to
   // the following block.
-  ASSERT_FALSE(TransformationMergeFunctionReturns(16, 100, 101, 0, 0, {{}})
+  ASSERT_FALSE(TransformationMergeFunctionReturns(16, 100, 200, 101, 0, 0, {{}})
                    .IsApplicable(context.get(), transformation_context));
 
   // Block %28 is the merge block of a loop containing a return instruction, but
@@ -160,7 +160,7 @@
   // not OpLabel, OpPhi or OpBranch).
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          18, 100, 101, 0, 0, {{MakeReturnMergingInfo(29, 102, 0, {{}})}})
+          18, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(29, 102, 0, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // Block %34 is the merge block of a loop containing a return instruction, but
@@ -168,21 +168,24 @@
   // that are not OpLabel, OpPhi or OpBranch).
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          20, 100, 101, 0, 0, {{MakeReturnMergingInfo(35, 102, 0, {{}})}})
+          20, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(35, 102, 0, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // Id %1000 cannot be found in the module and there is no id of the correct
   // type (float) available at the end of the entry block of function %21.
-  ASSERT_FALSE(TransformationMergeFunctionReturns(22, 100, 101, 102, 1000, {{}})
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(22, 100, 200, 101, 102, 1000, {{}})
+          .IsApplicable(context.get(), transformation_context));
 
   // Id %47 is of type float, while function %45 has return type int.
-  ASSERT_FALSE(TransformationMergeFunctionReturns(45, 100, 101, 102, 47, {{}})
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(45, 100, 200, 101, 102, 47, {{}})
+          .IsApplicable(context.get(), transformation_context));
 
   // Id %50 is not available at the end of the entry block of function %45.
-  ASSERT_FALSE(TransformationMergeFunctionReturns(45, 100, 101, 102, 50, {{}})
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(45, 100, 200, 101, 102, 50, {{}})
+          .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationMergeFunctionReturnsTest, MissingBooleans) {
@@ -235,8 +238,9 @@
     TransformationContext transformation_context(
         MakeUnique<FactManager>(context.get()), validator_options);
 
-    ASSERT_FALSE(TransformationMergeFunctionReturns(3, 100, 101, 0, 0, {{}})
-                     .IsApplicable(context.get(), transformation_context));
+    ASSERT_FALSE(
+        TransformationMergeFunctionReturns(3, 100, 200, 101, 0, 0, {{}})
+            .IsApplicable(context.get(), transformation_context));
   }
   {
     // OpConstantFalse is missing.
@@ -287,8 +291,9 @@
     TransformationContext transformation_context(
         MakeUnique<FactManager>(context.get()), validator_options);
 
-    ASSERT_FALSE(TransformationMergeFunctionReturns(3, 100, 101, 0, 0, {{}})
-                     .IsApplicable(context.get(), transformation_context));
+    ASSERT_FALSE(
+        TransformationMergeFunctionReturns(3, 100, 200, 101, 0, 0, {{}})
+            .IsApplicable(context.get(), transformation_context));
   }
 }
 
@@ -386,59 +391,65 @@
   // Fresh id %100 is used twice.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          17, 100, 100, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
+          17, 100, 200, 100, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // Fresh id %100 is used twice.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          17, 100, 101, 0, 0, {{MakeReturnMergingInfo(20, 100, 0, {{}})}})
+          17, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(20, 100, 0, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // %0 cannot be a fresh id for the new merge block.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          17, 100, 0, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
+          17, 100, 200, 0, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // %0 cannot be a fresh id for the new continue block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          17, 100, 0, 200, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // %0 cannot be a fresh id for the new header block.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          17, 0, 100, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
+          17, 0, 200, 100, 0, 0, {{MakeReturnMergingInfo(20, 101, 0, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // %0 cannot be a fresh id for the new |is_returning| instruction in an
   // existing merge block.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          17, 100, 101, 0, 0, {{MakeReturnMergingInfo(20, 0, 0, {{}})}})
+          17, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(20, 0, 0, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // %0 cannot be a fresh id for the new |return_val| instruction in the new
   // return block.
-  ASSERT_FALSE(
-      TransformationMergeFunctionReturns(
-          14, 100, 101, 0, 10, {{MakeReturnMergingInfo(27, 102, 103, {{}})}})
-          .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMergeFunctionReturns(
+                   14, 100, 200, 101, 0, 10,
+                   {{MakeReturnMergingInfo(27, 102, 103, {{}})}})
+                   .IsApplicable(context.get(), transformation_context));
 
   // %0 cannot be a fresh id for the new |maybe_return_val| instruction in an
   // existing merge block, inside a non-void function.
-  ASSERT_FALSE(
-      TransformationMergeFunctionReturns(
-          14, 100, 101, 102, 10, {{MakeReturnMergingInfo(27, 103, 0, {{}})}})
-          .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMergeFunctionReturns(
+                   14, 100, 200, 101, 102, 10,
+                   {{MakeReturnMergingInfo(27, 103, 0, {{}})}})
+                   .IsApplicable(context.get(), transformation_context));
 
   // Fresh id %102 is repeated.
-  ASSERT_FALSE(
-      TransformationMergeFunctionReturns(
-          14, 100, 101, 102, 10, {{MakeReturnMergingInfo(27, 102, 104, {{}})}})
-          .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMergeFunctionReturns(
+                   14, 100, 200, 101, 102, 10,
+                   {{MakeReturnMergingInfo(27, 102, 104, {{}})}})
+                   .IsApplicable(context.get(), transformation_context));
 
   // Id %11 (type int) does not have the correct type (float) for OpPhi
   // instruction %31.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          16, 100, 101, 0, 0,
+          16, 100, 200, 101, 0, 0,
           {{MakeReturnMergingInfo(36, 103, 104, {{{31, 11}, {32, 11}}})}})
           .IsApplicable(context.get(), transformation_context));
 
@@ -446,21 +457,21 @@
   // instruction %31.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          16, 100, 101, 0, 0,
+          16, 100, 200, 101, 0, 0,
           {{MakeReturnMergingInfo(36, 102, 0, {{{31, 11}, {32, 11}}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // Id %43 is not available at the end of the entry block.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          16, 100, 101, 0, 0,
+          16, 100, 200, 101, 0, 0,
           {{MakeReturnMergingInfo(36, 102, 0, {{{31, 44}, {32, 11}}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // There is not a mapping for id %31 (float OpPhi instruction in a loop merge
   // block) and no suitable id is available at the end of the entry block.
   ASSERT_FALSE(TransformationMergeFunctionReturns(
-                   16, 100, 101, 0, 0,
+                   16, 100, 200, 101, 0, 0,
                    {{MakeReturnMergingInfo(36, 102, 0, {{{32, 11}}})}})
                    .IsApplicable(context.get(), transformation_context));
 
@@ -468,7 +479,7 @@
   // available at the end of the entry block.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          16, 100, 101, 0, 0,
+          16, 100, 200, 101, 0, 0,
           {{MakeReturnMergingInfo(36, 102, 0, {{{31, 1000}, {32, 11}}})}})
           .IsApplicable(context.get(), transformation_context));
 }
@@ -560,7 +571,7 @@
 
   // The 0s are allowed because the function's return type is void.
   auto transformation1 =
-      TransformationMergeFunctionReturns(14, 100, 101, 0, 0, {{}});
+      TransformationMergeFunctionReturns(14, 100, 200, 101, 0, 0, {{}});
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation1, context.get(),
@@ -570,13 +581,14 @@
 
   // %12 is available at the end of the entry block of %19 (it is a global
   // variable).
-  ASSERT_TRUE(TransformationMergeFunctionReturns(19, 110, 111, 112, 12, {{}})
-                  .IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      TransformationMergeFunctionReturns(19, 110, 210, 111, 112, 12, {{}})
+          .IsApplicable(context.get(), transformation_context));
 
   // %1000 cannot be found in the module, but there is a suitable id available
   // at the end of the entry block (%12).
   auto transformation2 =
-      TransformationMergeFunctionReturns(19, 110, 111, 112, 1000, {{}});
+      TransformationMergeFunctionReturns(19, 110, 210, 111, 112, 1000, {{}});
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation2, context.get(),
@@ -586,13 +598,14 @@
 
   // %27 is available at the end of the entry block of %26 (it is a function
   // parameter).
-  ASSERT_TRUE(TransformationMergeFunctionReturns(26, 120, 121, 122, 27, {{}})
-                  .IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      TransformationMergeFunctionReturns(26, 120, 220, 121, 122, 27, {{}})
+          .IsApplicable(context.get(), transformation_context));
 
   // %1000 cannot be found in the module, but there is a suitable id available
   // at the end of the entry block (%27).
   auto transformation3 =
-      TransformationMergeFunctionReturns(26, 120, 121, 122, 1000, {{}});
+      TransformationMergeFunctionReturns(26, 120, 220, 121, 122, 1000, {{}});
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation3, context.get(),
@@ -602,13 +615,14 @@
 
   // %35 is available at the end of the entry block of %33 (it is in the entry
   // block).
-  ASSERT_TRUE(TransformationMergeFunctionReturns(26, 130, 131, 132, 27, {{}})
-                  .IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      TransformationMergeFunctionReturns(26, 130, 230, 131, 132, 27, {{}})
+          .IsApplicable(context.get(), transformation_context));
 
   // %1000 cannot be found in the module, but there is a suitable id available
   // at the end of the entry block (%35).
   auto transformation4 =
-      TransformationMergeFunctionReturns(33, 130, 131, 132, 1000, {{}});
+      TransformationMergeFunctionReturns(33, 130, 230, 131, 132, 1000, {{}});
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation4, context.get(),
@@ -642,8 +656,8 @@
          %15 = OpLabel
                OpBranch %100
         %100 = OpLabel
-               OpLoopMerge %101 %100 None
-               OpBranchConditional %11 %16 %100
+               OpLoopMerge %101 %200 None
+               OpBranch %16
          %16 = OpLabel
                OpSelectionMerge %17 None
                OpBranchConditional %11 %18 %17
@@ -653,13 +667,15 @@
                OpBranch %101
         %101 = OpLabel
                OpReturn
+        %200 = OpLabel
+               OpBranch %100
                OpFunctionEnd
          %19 = OpFunction %5 None %6
          %20 = OpLabel
                OpBranch %110
         %110 = OpLabel
-               OpLoopMerge %111 %110 None
-               OpBranchConditional %11 %21 %110
+               OpLoopMerge %111 %210 None
+               OpBranch %21
          %21 = OpLabel
                OpSelectionMerge %22 None
                OpBranchConditional %11 %23 %24
@@ -673,14 +689,16 @@
         %111 = OpLabel
         %112 = OpPhi %5 %12 %23 %25 %24
                OpReturnValue %112
+        %210 = OpLabel
+               OpBranch %110
                OpFunctionEnd
          %26 = OpFunction %7 None %8
          %27 = OpFunctionParameter %7
          %28 = OpLabel
                OpBranch %120
         %120 = OpLabel
-               OpLoopMerge %121 %120 None
-               OpBranchConditional %11 %29 %120
+               OpLoopMerge %121 %220 None
+               OpBranch %29
          %29 = OpLabel
                OpSelectionMerge %30 None
                OpBranchConditional %11 %31 %30
@@ -692,14 +710,16 @@
         %121 = OpLabel
         %122 = OpPhi %7 %27 %30 %32 %31
                OpReturnValue %122
+        %220 = OpLabel
+               OpBranch %120
                OpFunctionEnd
          %33 = OpFunction %7 None %9
          %34 = OpLabel
          %35 = OpConvertSToF %7 %12
                OpBranch %130
         %130 = OpLabel
-               OpLoopMerge %131 %130 None
-               OpBranchConditional %11 %36 %130
+               OpLoopMerge %131 %230 None
+               OpBranch %36
          %36 = OpLabel
                OpSelectionMerge %37 None
                OpBranchConditional %11 %38 %37
@@ -711,6 +731,8 @@
         %131 = OpLabel
         %132 = OpPhi %7 %35 %37 %39 %38
                OpReturnValue %132
+        %230 = OpLabel
+               OpBranch %130
                OpFunctionEnd
 )";
 
@@ -743,8 +765,8 @@
          %15 = OpLabel
                OpBranch %16
          %16 = OpLabel
-               OpLoopMerge %17 %16 None
-               OpBranchConditional %8 %18 %16
+               OpLoopMerge %17 %916 None
+               OpBranch %18
          %18 = OpLabel
                OpLoopMerge %19 %20 None
                OpBranchConditional %8 %19 %21
@@ -767,8 +789,8 @@
          %28 = OpLabel
                OpBranch %29
          %29 = OpLabel
-               OpLoopMerge %30 %29 None
-               OpBranchConditional %8 %30 %29
+               OpLoopMerge %30 %929 None
+               OpBranch %30
          %30 = OpLabel
                OpLoopMerge %31 %32 None
                OpBranch %33
@@ -792,6 +814,10 @@
                OpBranch %38
          %38 = OpLabel
                OpReturnValue %12
+        %916 = OpLabel
+               OpBranch %16
+        %929 = OpLabel
+               OpBranch %29
                OpFunctionEnd
 )";
 
@@ -805,7 +831,7 @@
       MakeUnique<FactManager>(context.get()), validator_options);
 
   auto transformation = TransformationMergeFunctionReturns(
-      14, 100, 101, 102, 11,
+      14, 100, 200, 101, 102, 11,
       {{MakeReturnMergingInfo(19, 103, 104, {{}}),
         MakeReturnMergingInfo(17, 105, 106, {{}}),
         MakeReturnMergingInfo(31, 107, 108, {{{35, 10}, {36, 12}}}),
@@ -841,11 +867,11 @@
          %15 = OpLabel
                OpBranch %100
         %100 = OpLabel
-               OpLoopMerge %101 %100 None
-               OpBranchConditional %8 %16 %100
+               OpLoopMerge %101 %200 None
+               OpBranch %16
          %16 = OpLabel
-               OpLoopMerge %17 %16 None
-               OpBranchConditional %8 %18 %16
+               OpLoopMerge %17 %916 None
+               OpBranch %18
          %18 = OpLabel
                OpLoopMerge %19 %20 None
                OpBranchConditional %8 %19 %21
@@ -872,8 +898,8 @@
          %28 = OpLabel
                OpBranch %29
          %29 = OpLabel
-               OpLoopMerge %30 %29 None
-               OpBranchConditional %8 %30 %29
+               OpLoopMerge %30 %929 None
+               OpBranch %30
          %30 = OpLabel
                OpLoopMerge %31 %32 None
                OpBranch %33
@@ -901,9 +927,15 @@
                OpBranchConditional %109 %101 %38
          %38 = OpLabel
                OpBranch %101
+        %916 = OpLabel
+               OpBranch %16
+        %929 = OpLabel
+               OpBranch %29
         %101 = OpLabel
         %102 = OpPhi %5 %106 %17 %110 %23 %12 %38
                OpReturnValue %102
+        %200 = OpLabel
+               OpBranch %100
                OpFunctionEnd
 )";
 
@@ -997,7 +1029,7 @@
   // No mapping from merge block %16 to fresh ids is given, so overflow ids are
   // needed.
   auto transformation1 =
-      TransformationMergeFunctionReturns(12, 100, 101, 102, 10, {{}});
+      TransformationMergeFunctionReturns(12, 100, 200, 101, 102, 10, {{}});
 
 #ifndef NDEBUG
   ASSERT_DEATH(
@@ -1016,7 +1048,7 @@
   // No mapping from merge block %27 to fresh ids is given, so overflow ids are
   // needed.
   auto transformation2 =
-      TransformationMergeFunctionReturns(24, 110, 111, 0, 0, {{}});
+      TransformationMergeFunctionReturns(24, 110, 210, 111, 0, 0, {{}});
 
 #ifndef NDEBUG
   ASSERT_DEATH(
@@ -1054,8 +1086,8 @@
          %13 = OpLabel
                OpBranch %100
         %100 = OpLabel
-               OpLoopMerge %101 %100 None
-               OpBranchConditional %8 %14 %100
+               OpLoopMerge %101 %200 None
+               OpBranch %14
          %14 = OpLabel
          %15 = OpIAdd %5 %10 %10
                OpLoopMerge %16 %17 None
@@ -1081,13 +1113,15 @@
         %101 = OpLabel
         %102 = OpPhi %5 %1001 %16 %22 %23
                OpReturnValue %102
+        %200 = OpLabel
+               OpBranch %100
                OpFunctionEnd
          %24 = OpFunction %3 None %4
          %25 = OpLabel
                OpBranch %110
         %110 = OpLabel
-               OpLoopMerge %111 %110 None
-               OpBranchConditional %8 %26 %110
+               OpLoopMerge %111 %210 None
+               OpBranch %26
          %26 = OpLabel
                OpLoopMerge %27 %28 None
                OpBranch %29
@@ -1110,6 +1144,8 @@
                OpBranch %111
         %111 = OpLabel
                OpReturn
+        %210 = OpLabel
+               OpBranch %110
                OpFunctionEnd
 )";
 
@@ -1179,7 +1215,7 @@
   // corresponding mapping.
 
   auto transformation = TransformationMergeFunctionReturns(
-      12, 101, 102, 0, 0,
+      12, 101, 200, 102, 0, 0,
       {{MakeReturnMergingInfo(17, 103, 0, {{{25, 7}, {35, 8}}})}});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
@@ -1212,8 +1248,8 @@
          %15 = OpConvertSToF %10 %13
                OpBranch %101
         %101 = OpLabel
-               OpLoopMerge %102 %101 None
-               OpBranchConditional %6 %16 %101
+               OpLoopMerge %102 %200 None
+               OpBranch %16
          %16 = OpLabel
                OpLoopMerge %17 %18 None
                OpBranch %19
@@ -1238,6 +1274,8 @@
                OpBranch %102
         %102 = OpLabel
                OpReturn
+        %200 = OpLabel
+               OpBranch %101
                OpFunctionEnd
 )";
 
@@ -1324,14 +1362,14 @@
   // block %14 is added.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}})
+          2, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // In function %18, The definition of id %26 will still dominate its use in
   // instruction %27 (inside merge block %21), because %27 is an OpPhi
   // instruction.
   auto transformation = TransformationMergeFunctionReturns(
-      18, 100, 101, 0, 0, {{MakeReturnMergingInfo(21, 102, 103, {{}})}});
+      18, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(21, 102, 103, {{}})}});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
@@ -1376,8 +1414,8 @@
          %19 = OpLabel
                OpBranch %100
         %100 = OpLabel
-               OpLoopMerge %101 %100 None
-               OpBranchConditional %6 %20 %100
+               OpLoopMerge %101 %200 None
+               OpBranch %20
          %20 = OpLabel
                OpLoopMerge %21 %22 None
                OpBranch %23
@@ -1399,6 +1437,8 @@
                OpBranch %101
         %101 = OpLabel
                OpReturn
+        %200 = OpLabel
+               OpBranch %100
                OpFunctionEnd
 )";
 
@@ -1482,17 +1522,17 @@
   // block %14 to merge block %10 is added.
   ASSERT_FALSE(
       TransformationMergeFunctionReturns(
-          2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}})
+          2, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}})
           .IsApplicable(context.get(), transformation_context));
 
   // In function %18, the definition of id %26 will not dominate its use in
   // instruction %28 (inside block %27) after a new branch from return
   // block %25 to merge block %21 is added.
-  ASSERT_FALSE(TransformationMergeFunctionReturns(
-                   2, 100, 101, 0, 0,
-                   {{MakeReturnMergingInfo(10, 102, 0, {{}}),
-                     MakeReturnMergingInfo(21, 103, 0, {{}})}})
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          2, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 0, {{}}),
+                                    MakeReturnMergingInfo(21, 103, 0, {{}})}})
+          .IsApplicable(context.get(), transformation_context));
 }
 
 TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules3) {
@@ -1556,7 +1596,7 @@
   // fact that the id definition dominates the uses does not depend on it
   // dominating the merge block.
   auto transformation = TransformationMergeFunctionReturns(
-      2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}});
+      2, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
@@ -1579,8 +1619,8 @@
           %8 = OpLabel
                OpBranch %100
         %100 = OpLabel
-               OpLoopMerge %101 %100 None
-               OpBranchConditional %6 %9 %100
+               OpLoopMerge %101 %200 None
+               OpBranch %9
           %9 = OpLabel
                OpLoopMerge %10 %11 None
                OpBranch %12
@@ -1608,6 +1648,8 @@
                OpBranch %101
         %101 = OpLabel
                OpReturn
+        %200 = OpLabel
+               OpBranch %100
                OpFunctionEnd
 )";
 
@@ -1700,7 +1742,7 @@
   // instruction %19 after the transformation is applied, because %13 dominates
   // all of the return blocks.
   auto transformation = TransformationMergeFunctionReturns(
-      2, 100, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}});
+      2, 100, 200, 101, 0, 0, {{MakeReturnMergingInfo(10, 102, 103, {{}})}});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
@@ -1710,10 +1752,10 @@
   // In function %20, the definition of id %28 will not dominate its use in
   // instruction %32 after the transformation is applied, because %28 dominates
   // only one of the return blocks.
-  ASSERT_FALSE(
-      TransformationMergeFunctionReturns(
-          20, 100, 101, 0, 0, {{MakeReturnMergingInfo(23, 102, 103, {{}})}})
-          .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMergeFunctionReturns(
+                   20, 100, 200, 101, 0, 0,
+                   {{MakeReturnMergingInfo(23, 102, 103, {{}})}})
+                   .IsApplicable(context.get(), transformation_context));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1731,8 +1773,8 @@
           %8 = OpLabel
                OpBranch %100
         %100 = OpLabel
-               OpLoopMerge %101 %100 None
-               OpBranchConditional %6 %9 %100
+               OpLoopMerge %101 %200 None
+               OpBranch %9
           %9 = OpLabel
                OpLoopMerge %10 %11 None
                OpBranch %12
@@ -1759,6 +1801,8 @@
                OpBranch %101
         %101 = OpLabel
                OpReturn
+        %200 = OpLabel
+               OpBranch %100
                OpFunctionEnd
          %20 = OpFunction %3 None %4
          %21 = OpLabel
@@ -1830,7 +1874,7 @@
       MakeUnique<FactManager>(context.get()), validator_options);
 
   auto transformation =
-      TransformationMergeFunctionReturns(2, 100, 101, 0, 0, {});
+      TransformationMergeFunctionReturns(2, 100, 200, 101, 0, 0, {});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
@@ -1863,8 +1907,8 @@
           %8 = OpLabel
                OpBranch %100
         %100 = OpLabel
-               OpLoopMerge %101 %100 None
-               OpBranchConditional %6 %9 %100
+               OpLoopMerge %101 %200 None
+               OpBranch %9
           %9 = OpLabel
          %10 = OpPhi %5 %6 %100
                OpSelectionMerge %11 None
@@ -1875,6 +1919,8 @@
                OpBranch %101
         %101 = OpLabel
                OpReturn
+        %200 = OpLabel
+               OpBranch %100
                OpFunctionEnd
 )";
 
diff --git a/test/fuzz/transformation_mutate_pointer_test.cpp b/test/fuzz/transformation_mutate_pointer_test.cpp
index ae42310..e869efa 100644
--- a/test/fuzz/transformation_mutate_pointer_test.cpp
+++ b/test/fuzz/transformation_mutate_pointer_test.cpp
@@ -26,6 +26,7 @@
 TEST(TransformationMutatePointerTest, BasicTest) {
   std::string shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -60,7 +61,6 @@
          %23 = OpTypePointer Output %6
          %24 = OpVariable %23 Output
          %27 = OpTypeFunction %2 %13
-         %32 = OpUndef %16
          %33 = OpConstantNull %16
           %4 = OpFunction %2 None %3
           %5 = OpLabel
@@ -110,10 +110,6 @@
   ASSERT_FALSE(TransformationMutatePointer(11, 70, insert_before)
                    .IsApplicable(context.get(), transformation_context));
 
-  // |pointer_id| is a result id of OpUndef.
-  ASSERT_FALSE(TransformationMutatePointer(32, 70, insert_before)
-                   .IsApplicable(context.get(), transformation_context));
-
   // |pointer_id| is a result id of OpConstantNull.
   ASSERT_FALSE(TransformationMutatePointer(33, 70, insert_before)
                    .IsApplicable(context.get(), transformation_context));
@@ -163,6 +159,7 @@
 
   std::string after_transformation = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main"
@@ -197,7 +194,6 @@
          %23 = OpTypePointer Output %6
          %24 = OpVariable %23 Output
          %27 = OpTypeFunction %2 %13
-         %32 = OpUndef %16
          %33 = OpConstantNull %16
           %4 = OpFunction %2 None %3
           %5 = OpLabel
diff --git a/test/fuzz/transformation_outline_function_test.cpp b/test/fuzz/transformation_outline_function_test.cpp
index c567680..cc62049 100644
--- a/test/fuzz/transformation_outline_function_test.cpp
+++ b/test/fuzz/transformation_outline_function_test.cpp
@@ -3207,6 +3207,45 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationOutlineFunctionTest, NoOutlineWithUnreachableBlocks) {
+  // This checks that outlining will not be performed if a node in the region
+  // has an unreachable predecessor.
+
+  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
+          %7 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+               OpReturn
+          %6 = OpLabel
+               OpBranch %5
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  TransformationOutlineFunction transformation(5, 5, /* not relevant */ 200,
+                                               100, 101, 102, 103,
+                                               /* not relevant */ 201, {}, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_permute_phi_operands_test.cpp b/test/fuzz/transformation_permute_phi_operands_test.cpp
index 2843cfc..0774dcf 100644
--- a/test/fuzz/transformation_permute_phi_operands_test.cpp
+++ b/test/fuzz/transformation_permute_phi_operands_test.cpp
@@ -60,6 +60,7 @@
                OpBranch %17
          %17 = OpLabel
          %25 = OpPhi %6 %20 %16 %24 %21
+         %30 = OpIAdd %6 %25 %25
                OpStore %8 %25
                OpReturn
                OpFunctionEnd
@@ -105,6 +106,47 @@
       transformation.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
+  // Check that the def-use manager knows that the phi instruction's ids have
+  // been permuted.
+  std::vector<std::pair<uint32_t, uint32_t>> phi_operand_to_new_operand_index =
+      {{20, 4}, {16, 5}, {24, 2}, {21, 3}};
+  for (std::pair<uint32_t, uint32_t>& entry :
+       phi_operand_to_new_operand_index) {
+    context->get_def_use_mgr()->WhileEachUse(
+        entry.first,
+        [&entry](opt::Instruction* inst, uint32_t operand_index) -> bool {
+          if (inst->result_id() == 25) {
+            EXPECT_EQ(entry.second, operand_index);
+            return false;
+          }
+          return true;
+        });
+  }
+  bool found_use_in_store = false;
+  bool found_use_in_add_lhs = false;
+  bool found_use_in_add_rhs = false;
+  context->get_def_use_mgr()->ForEachUse(
+      25, [&found_use_in_store, &found_use_in_add_lhs, &found_use_in_add_rhs](
+              opt::Instruction* inst, uint32_t operand_index) {
+        if (inst->opcode() == SpvOpStore) {
+          ASSERT_FALSE(found_use_in_store);
+          found_use_in_store = true;
+        } else {
+          ASSERT_EQ(SpvOpIAdd, inst->opcode());
+          if (operand_index == 2) {
+            ASSERT_FALSE(found_use_in_add_lhs);
+            found_use_in_add_lhs = true;
+          } else {
+            ASSERT_EQ(3, operand_index);
+            ASSERT_FALSE(found_use_in_add_rhs);
+            found_use_in_add_rhs = true;
+          }
+        }
+      });
+  ASSERT_TRUE(found_use_in_store);
+  ASSERT_TRUE(found_use_in_add_lhs);
+  ASSERT_TRUE(found_use_in_add_rhs);
+
   std::string after_transformation = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -142,6 +184,7 @@
                OpBranch %17
          %17 = OpLabel
          %25 = OpPhi %6 %24 %21 %20 %16
+         %30 = OpIAdd %6 %25 %25
                OpStore %8 %25
                OpReturn
                OpFunctionEnd
diff --git a/test/fuzz/transformation_push_id_through_variable_test.cpp b/test/fuzz/transformation_push_id_through_variable_test.cpp
index cd45c4c..b0fff58 100644
--- a/test/fuzz/transformation_push_id_through_variable_test.cpp
+++ b/test/fuzz/transformation_push_id_through_variable_test.cpp
@@ -26,6 +26,7 @@
 TEST(TransformationPushIdThroughVariableTest, IsApplicable) {
   std::string reference_shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %92 %52 %53
@@ -50,7 +51,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
          %80 = OpConstantComposite %8 %21 %24
@@ -257,6 +257,7 @@
 TEST(TransformationPushIdThroughVariableTest, Apply) {
   std::string reference_shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %92 %52 %53
@@ -281,7 +282,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
          %80 = OpConstantComposite %8 %21 %24
@@ -341,7 +341,26 @@
   auto transformation = TransformationPushIdThroughVariable(
       value_id, value_synonym_id, variable_id, variable_storage_class,
       initializer_id, instruction_descriptor);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(value_synonym_id));
+  ASSERT_EQ(nullptr, context->get_instr_block(value_synonym_id));
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(variable_id));
+  ASSERT_EQ(nullptr, context->get_instr_block(variable_id));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpLoad,
+            context->get_def_use_mgr()->GetDef(value_synonym_id)->opcode());
+  ASSERT_EQ(36, context->get_instr_block(value_synonym_id)->id());
+  ASSERT_EQ(SpvOpVariable,
+            context->get_def_use_mgr()->GetDef(variable_id)->opcode());
+  ASSERT_EQ(5, context->get_instr_block(variable_id)->id());
+  uint32_t variable_use_count = 0;
+  context->get_def_use_mgr()->ForEachUse(
+      variable_id,
+      [&variable_use_count](opt::Instruction* inst, uint32_t /*unused*/) {
+        ASSERT_TRUE(inst->opcode() == SpvOpLoad ||
+                    inst->opcode() == SpvOpStore);
+        variable_use_count++;
+      });
+  ASSERT_EQ(2, variable_use_count);
 
   value_id = 21;
   value_synonym_id = 102;
@@ -400,6 +419,7 @@
 
   std::string variant_shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %92 %52 %53 %109 %111
@@ -424,7 +444,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
          %80 = OpConstantComposite %8 %21 %24
@@ -500,6 +519,7 @@
 TEST(TransformationPushIdThroughVariableTest, AddSynonymsForRelevantIds) {
   std::string reference_shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %92 %52 %53
@@ -524,7 +544,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
          %80 = OpConstantComposite %8 %21 %24
@@ -601,6 +620,7 @@
 TEST(TransformationPushIdThroughVariableTest, DontAddSynonymsForIrrelevantIds) {
   std::string reference_shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %92 %52 %53
@@ -625,7 +645,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
          %80 = OpConstantComposite %8 %21 %24
diff --git a/test/fuzz/transformation_replace_branch_from_dead_block_with_exit_test.cpp b/test/fuzz/transformation_replace_branch_from_dead_block_with_exit_test.cpp
index 4532503..6bba14f 100644
--- a/test/fuzz/transformation_replace_branch_from_dead_block_with_exit_test.cpp
+++ b/test/fuzz/transformation_replace_branch_from_dead_block_with_exit_test.cpp
@@ -566,6 +566,302 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
+     DominatorAfterDeadBlockSuccessor) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %7 %9 %10
+          %9 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpCopyObject %6 %7
+               OpBranch %8
+         %10 = OpLabel
+               OpBranch %13
+          %8 = OpLabel
+               OpReturn
+         %13 = OpLabel
+               OpBranch %8
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(9);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(11);
+
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(11, SpvOpUnreachable, 0)
+          .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
+     UnreachableSuccessor) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %7 %9 %10
+          %9 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpCopyObject %6 %7
+               OpBranch %8
+         %10 = OpLabel
+               OpReturn
+          %8 = OpLabel
+               OpReturn
+         %13 = OpLabel
+               OpBranch %8
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(9);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(11);
+
+  TransformationReplaceBranchFromDeadBlockWithExit transformation(
+      11, SpvOpUnreachable, 0);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %7 %9 %10
+          %9 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpCopyObject %6 %7
+               OpUnreachable
+         %10 = OpLabel
+               OpReturn
+          %8 = OpLabel
+               OpReturn
+         %13 = OpLabel
+               OpBranch %8
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
+     DeadBlockAfterItsSuccessor) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %7 %9 %10
+          %9 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpCopyObject %6 %7
+               OpBranch %8
+         %10 = OpLabel
+               OpBranch %13
+          %8 = OpLabel
+               OpReturn
+         %13 = OpLabel
+               OpBranch %8
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(10);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(13);
+
+  TransformationReplaceBranchFromDeadBlockWithExit transformation(
+      13, SpvOpUnreachable, 0);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %7 %9 %10
+          %9 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpCopyObject %6 %7
+               OpBranch %8
+         %10 = OpLabel
+               OpBranch %13
+          %8 = OpLabel
+               OpReturn
+         %13 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
+     BranchToOuterMergeBlock) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+         %15 = OpTypeInt 32 0
+         %14 = OpUndef %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %8 None
+               OpSwitch %14 %9 1 %8
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %7 %11 %10
+          %8 = OpLabel
+               OpReturn
+         %11 = OpLabel
+               OpBranch %8
+         %10 = OpLabel
+               OpBranch %8
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(10);
+
+  TransformationReplaceBranchFromDeadBlockWithExit transformation(
+      10, SpvOpUnreachable, 0);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+         %15 = OpTypeInt 32 0
+         %14 = OpUndef %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %8 None
+               OpSwitch %14 %9 1 %8
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %7 %11 %10
+          %8 = OpLabel
+               OpReturn
+         %11 = OpLabel
+               OpBranch %8
+         %10 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_id_with_synonym_test.cpp b/test/fuzz/transformation_replace_id_with_synonym_test.cpp
index 5c10fc5..b33dd48 100644
--- a/test/fuzz/transformation_replace_id_with_synonym_test.cpp
+++ b/test/fuzz/transformation_replace_id_with_synonym_test.cpp
@@ -303,10 +303,18 @@
   auto global_constant_synonym = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(19, MakeInstructionDescriptor(47, SpvOpStore, 0), 1),
       210);
+  uint32_t num_uses_of_original_id_before_replacement =
+      context->get_def_use_mgr()->NumUses(19);
+  uint32_t num_uses_of_synonym_before_replacement =
+      context->get_def_use_mgr()->NumUses(210);
   ASSERT_TRUE(global_constant_synonym.IsApplicable(context.get(),
                                                    transformation_context));
   ApplyAndCheckFreshIds(global_constant_synonym, context.get(),
                         &transformation_context);
+  ASSERT_EQ(num_uses_of_original_id_before_replacement - 1,
+            context->get_def_use_mgr()->NumUses(19));
+  ASSERT_EQ(num_uses_of_synonym_before_replacement + 1,
+            context->get_def_use_mgr()->NumUses(210));
   ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
                                                kConsoleMessageConsumer));
 
@@ -1788,6 +1796,383 @@
                    .IsApplicable(context.get(), transformation_context));
 }
 
+TEST(TransformationReplaceIdWithSynonymTest,
+     AtomicScopeAndMemorySemanticsMustBeConstant) {
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 320
+               OpSourceExtension "GL_KHR_memory_scope_semantics"
+               OpMemberDecorate %9 0 Offset 0
+               OpDecorate %9 Block
+               OpDecorate %11 DescriptorSet 0
+               OpDecorate %11 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %17 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %9
+         %11 = OpVariable %10 StorageBuffer
+         %86 = OpTypeStruct %17
+         %87 = OpTypePointer Workgroup %86         
+         %88 = OpVariable %87 Workgroup
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 2
+         %16 = OpConstant %6 64
+         %89 = OpTypePointer Workgroup %17
+         %18 = OpConstant %17 1
+         %19 = OpConstant %17 0
+         %20 = OpConstant %17 64
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+        %100 = OpCopyObject %6 %15 ; A non-constant version of %15
+        %101 = OpCopyObject %17 %20 ; A non-constant version of %20
+         %14 = OpAccessChain %13 %11 %12
+         %90 = OpAccessChain %89 %88 %19
+         %21 = OpAtomicLoad %6 %14 %15 %20
+         %22 = OpAtomicExchange %6 %14 %15 %20 %16
+         %23 = OpAtomicCompareExchange %6 %14 %15 %20 %12 %16 %15
+         %24 = OpAtomicIIncrement %6 %14 %15 %20
+         %25 = OpAtomicIDecrement %6 %14 %15 %20
+         %26 = OpAtomicIAdd %6  %14 %15 %20 %16
+         %27 = OpAtomicISub %6  %14 %15 %20 %16
+         %28 = OpAtomicSMin %6  %14 %15 %20 %16
+         %29 = OpAtomicUMin %17 %90 %15 %20 %18
+         %30 = OpAtomicSMax %6  %14 %15 %20 %15
+         %31 = OpAtomicUMax %17 %90 %15 %20 %18
+         %32 = OpAtomicAnd  %6  %14 %15 %20 %16
+         %33 = OpAtomicOr   %6  %14 %15 %20 %16
+         %34 = OpAtomicXor  %6  %14 %15 %20 %16
+               OpStore %8 %21
+               OpAtomicStore %14 %15 %20 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Tell the fact manager that %100 and %15 are synonymous
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(15, {}));
+
+  // Tell the fact manager that %101 and %20 are synonymous
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(101, {}), MakeDataDescriptor(20, {}));
+  // OpAtomicLoad
+  const auto& scope_operand = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(21), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(21), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand, 101)
+                   .IsApplicable(context.get(), transformation_context));
+  // OpAtomicExchange.
+  const auto& scope_operand2 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(22), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand2, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand2 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(22), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand2, 101)
+                   .IsApplicable(context.get(), transformation_context));
+  // OpAtomicCompareExchange.
+  const auto& scope_operand3 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(23), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand3, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_equal_operand3 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(23), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_equal_operand3, 101)
+                   .IsApplicable(context.get(), transformation_context));
+  const auto& semantics_unequal_operand3 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(23), 3);
+  ASSERT_FALSE(
+      TransformationReplaceIdWithSynonym(semantics_unequal_operand3, 101)
+          .IsApplicable(context.get(), transformation_context));
+  // OpAtomicIIncrement.
+  const auto& scope_operand4 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(24), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand4, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand4 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(24), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand4, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpAtomicIDecrement.
+  const auto& scope_operand5 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(25), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand5, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand5 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(25), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand5, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpAtomicIAdd.
+  const auto& scope_operand6 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(26), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand6, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand6 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(26), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand6, 101)
+                   .IsApplicable(context.get(), transformation_context));
+  // OpAtomicISub
+  const auto& scope_operand8 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(27), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand8, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand8 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(27), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand8, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpAtomicSMin
+  const auto& scope_operand9 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(28), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand9, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand9 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(28), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand9, 101)
+                   .IsApplicable(context.get(), transformation_context));
+  // OpAtomicUMin
+  const auto& scope_operand10 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(29), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand10, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand10 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(29), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand10, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpAtomicSMax
+  const auto& scope_operand11 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(30), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand11, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand11 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(30), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand11, 101)
+                   .IsApplicable(context.get(), transformation_context));
+  // OpAtomicUMax
+  const auto& scope_operand12 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(31), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand12, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand12 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(31), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand12, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpAtomicAnd
+  const auto& scope_operand13 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(32), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand13, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand13 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(32), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand13, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpAtomicOr
+  const auto& scope_operand14 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(33), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand14, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand14 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(33), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand14, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // OpAtomicXor
+  const auto& scope_operand15 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(34), 1);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(scope_operand15, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  const auto& semantics_operand15 = MakeIdUseDescriptorFromUse(
+      context.get(), context->get_def_use_mgr()->GetDef(34), 2);
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(semantics_operand15, 101)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/4345): Improve this
+//  test so that it covers more atomic operations, and enable the test once the
+//  issue is fixed.
+TEST(TransformationReplaceIdWithSynonymTest,
+     DISABLED_SignOfAtomicScopeAndMemorySemanticsDoesNotMatter) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/4345): both the
+  //  GLSL comment and the corresponding SPIR-V should be updated to cover a
+  //  larger number of atomic operations.
+  // The following SPIR-V came from this GLSL, edited to add some synonyms:
+  //
+  // #version 320 es
+  //
+  // #extension GL_KHR_memory_scope_semantics : enable
+  //
+  // layout(set = 0, binding = 0) buffer Buf {
+  //   int x;
+  // };
+  //
+  // void main() {
+  //   int tmp = atomicLoad(x,
+  //                        gl_ScopeWorkgroup,
+  //                        gl_StorageSemanticsBuffer,
+  //                        gl_SemanticsRelaxed);
+  // }
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 320
+               OpSourceExtension "GL_KHR_memory_scope_semantics"
+               OpMemberDecorate %9 0 Offset 0
+               OpDecorate %9 Block
+               OpDecorate %11 DescriptorSet 0
+               OpDecorate %11 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %9
+         %11 = OpVariable %10 StorageBuffer
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 2
+         %16 = OpConstant %6 64
+         %17 = OpTypeInt 32 0
+        %100 = OpConstant %17 2 ; The same as %15, but with unsigned int type
+         %18 = OpConstant %17 1
+         %19 = OpConstant %17 0
+         %20 = OpConstant %17 64
+        %101 = OpConstant %6 64 ; The same as %20, but with signed int type
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %14 = OpAccessChain %13 %11 %12
+         %21 = OpAtomicLoad %6 %14 %15 %20
+               OpStore %8 %21
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Tell the fact manager that %100 and %15 are synonymous
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(15, {}));
+
+  // Tell the fact manager that %101 and %20 are synonymous
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(101, {}), MakeDataDescriptor(20, {}));
+
+  {
+    const auto& scope_operand = MakeIdUseDescriptorFromUse(
+        context.get(), context->get_def_use_mgr()->GetDef(21), 1);
+    TransformationReplaceIdWithSynonym replace_scope(scope_operand, 100);
+    ASSERT_TRUE(
+        replace_scope.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(replace_scope, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  {
+    const auto& semantics_operand = MakeIdUseDescriptorFromUse(
+        context.get(), context->get_def_use_mgr()->GetDef(21), 2);
+    TransformationReplaceIdWithSynonym replace_semantics(semantics_operand,
+                                                         101);
+    ASSERT_TRUE(
+        replace_semantics.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(replace_semantics, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 320
+               OpSourceExtension "GL_KHR_memory_scope_semantics"
+               OpMemberDecorate %9 0 Offset 0
+               OpDecorate %9 Block
+               OpDecorate %11 DescriptorSet 0
+               OpDecorate %11 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %9
+         %11 = OpVariable %10 StorageBuffer
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 2
+         %16 = OpConstant %6 64
+         %17 = OpTypeInt 32 0
+        %100 = OpConstant %17 2
+         %18 = OpConstant %17 1
+         %19 = OpConstant %17 0
+         %20 = OpConstant %17 64
+        %101 = OpConstant %6 64
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %14 = OpAccessChain %13 %11 %12
+         %21 = OpAtomicLoad %6 %14 %100 %101
+               OpStore %8 %21
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_set_loop_control_test.cpp b/test/fuzz/transformation_set_loop_control_test.cpp
index 3312a67..88b4aab 100644
--- a/test/fuzz/transformation_set_loop_control_test.cpp
+++ b/test/fuzz/transformation_set_loop_control_test.cpp
@@ -947,7 +947,9 @@
 
   for (auto env :
        {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2,
-        SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_5}) {
+        SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_5,
+        SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_1_SPIRV_1_4,
+        SPV_ENV_VULKAN_1_2}) {
     const auto consumer = nullptr;
     const auto context =
         BuildModule(env, consumer, shader, kFuzzAssembleOption);
@@ -956,23 +958,33 @@
         context.get(), validator_options, kConsoleMessageConsumer));
     TransformationContext transformation_context(
         MakeUnique<FactManager>(context.get()), validator_options);
-    TransformationSetLoopControl transformation(
-        10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4);
+    TransformationSetLoopControl peel_count(10, SpvLoopControlPeelCountMask, 4,
+                                            0);
+    TransformationSetLoopControl partial_count(
+        10, SpvLoopControlPartialCountMask, 0, 4);
 
     switch (env) {
       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_VULKAN_1_0:
+      case SPV_ENV_VULKAN_1_1:
         // PeelCount and PartialCount were introduced in SPIRV 1.4, so are not
         // valid in the context of older versions.
         ASSERT_FALSE(
-            transformation.IsApplicable(context.get(), transformation_context));
+            peel_count.IsApplicable(context.get(), transformation_context));
+        ASSERT_FALSE(
+            partial_count.IsApplicable(context.get(), transformation_context));
         break;
       case SPV_ENV_UNIVERSAL_1_4:
       case SPV_ENV_UNIVERSAL_1_5:
+      case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
+      case SPV_ENV_VULKAN_1_2:
         ASSERT_TRUE(
-            transformation.IsApplicable(context.get(), transformation_context));
+            peel_count.IsApplicable(context.get(), transformation_context));
+        ASSERT_TRUE(
+            partial_count.IsApplicable(context.get(), transformation_context));
         break;
       default:
         assert(false && "Unhandled environment");
diff --git a/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
index 4763d7a..29e57f4 100644
--- a/test/fuzz/transformation_set_memory_operands_mask_test.cpp
+++ b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
@@ -90,187 +90,205 @@
                OpFunctionEnd
   )";
 
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  spvtools::ValidatorOptions validator_options;
-  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
-                                               kConsoleMessageConsumer));
-  TransformationContext transformation_context(
-      MakeUnique<FactManager>(context.get()), validator_options);
-  // Not OK: the instruction is not a memory access.
-  ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                   MakeInstructionDescriptor(21, SpvOpAccessChain, 0),
-                   SpvMemoryAccessMaskNone, 0)
-                   .IsApplicable(context.get(), transformation_context));
+  for (auto env :
+       {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2,
+        SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1}) {
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    spvtools::ValidatorOptions validator_options;
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
 
-  // Not OK to remove Aligned
-  ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                   MakeInstructionDescriptor(147, SpvOpLoad, 0),
-                   SpvMemoryAccessVolatileMask | SpvMemoryAccessNontemporalMask,
-                   0)
-                   .IsApplicable(context.get(), transformation_context));
+#ifndef NDEBUG
+    {
+      // Not OK: multiple operands are not supported pre SPIR-V 1.4.
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 3),
+          SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 1);
+      ASSERT_DEATH(
+          transformation.IsApplicable(context.get(), transformation_context),
+          "Multiple memory operand masks are not supported");
+    }
+#endif
 
-  {
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(147, SpvOpLoad, 0),
-        SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
+    // Not OK: the instruction is not a memory access.
+    ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+                     MakeInstructionDescriptor(21, SpvOpAccessChain, 0),
+                     SpvMemoryAccessMaskNone, 0)
+                     .IsApplicable(context.get(), transformation_context));
+
+    // Not OK to remove Aligned
+    ASSERT_FALSE(
+        TransformationSetMemoryOperandsMask(
+            MakeInstructionDescriptor(147, SpvOpLoad, 0),
+            SpvMemoryAccessVolatileMask | SpvMemoryAccessNontemporalMask, 0)
+            .IsApplicable(context.get(), transformation_context));
+
+    {
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(147, SpvOpLoad, 0),
+          SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
+
+    // Not OK to remove Aligned
+    ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+                     MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+                     SpvMemoryAccessMaskNone, 0)
+                     .IsApplicable(context.get(), transformation_context));
+
+    // OK: leaves the mask as is
+    ASSERT_TRUE(TransformationSetMemoryOperandsMask(
+                    MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+                    SpvMemoryAccessAlignedMask, 0)
+                    .IsApplicable(context.get(), transformation_context));
+
+    {
+      // OK: adds Nontemporal and Volatile
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+          SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask |
+              SpvMemoryAccessVolatileMask,
+          0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
+
+    // Not OK to remove Volatile
+    ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+                     MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+                     SpvMemoryAccessNontemporalMask, 0)
+                     .IsApplicable(context.get(), transformation_context));
+
+    // Not OK to add Aligned
+    ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+                     MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+                     SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask,
+                     0)
+                     .IsApplicable(context.get(), transformation_context));
+
+    {
+      // OK: adds Nontemporal
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+          SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
+
+    {
+      // OK: adds Nontemporal (creates new operand)
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 2),
+          SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
+
+    {
+      // OK: adds Nontemporal and Volatile
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
+          SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
+
+    {
+      // OK: removes Nontemporal, adds Volatile
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(148, SpvOpStore, 0),
+          SpvMemoryAccessVolatileMask, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
+
+    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 %7 "Point3D"
+                 OpMemberName %7 0 "x"
+                 OpMemberName %7 1 "y"
+                 OpMemberName %7 2 "z"
+                 OpName %12 "global_points"
+                 OpName %15 "block"
+                 OpMemberName %15 0 "in_points"
+                 OpMemberName %15 1 "in_point"
+                 OpName %17 ""
+                 OpName %133 "local_points"
+                 OpMemberDecorate %7 0 Offset 0
+                 OpMemberDecorate %7 1 Offset 4
+                 OpMemberDecorate %7 2 Offset 8
+                 OpDecorate %10 ArrayStride 16
+                 OpMemberDecorate %15 0 Offset 0
+                 OpMemberDecorate %15 1 Offset 192
+                 OpDecorate %15 Block
+                 OpDecorate %17 DescriptorSet 0
+                 OpDecorate %17 Binding 0
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeStruct %6 %6 %6
+            %8 = OpTypeInt 32 0
+            %9 = OpConstant %8 12
+           %10 = OpTypeArray %7 %9
+           %11 = OpTypePointer Private %10
+           %12 = OpVariable %11 Private
+           %15 = OpTypeStruct %10 %7
+           %16 = OpTypePointer Uniform %15
+           %17 = OpVariable %16 Uniform
+           %18 = OpTypeInt 32 1
+           %19 = OpConstant %18 0
+           %20 = OpTypePointer Uniform %10
+           %24 = OpTypePointer Private %7
+           %27 = OpTypePointer Private %6
+           %30 = OpConstant %18 1
+          %132 = OpTypePointer Function %10
+          %135 = OpTypePointer Uniform %7
+          %145 = OpTypePointer Function %7
+            %4 = OpFunction %2 None %3
+            %5 = OpLabel
+          %133 = OpVariable %132 Function
+           %21 = OpAccessChain %20 %17 %19
+                 OpCopyMemory %12 %21 Aligned|Nontemporal|Volatile 16
+                 OpCopyMemory %133 %12 Nontemporal|Volatile
+                 OpCopyMemory %133 %12 Nontemporal|Volatile
+          %136 = OpAccessChain %135 %17 %30
+          %138 = OpAccessChain %24 %12 %19
+                 OpCopyMemory %138 %136 Nontemporal|Volatile
+          %146 = OpAccessChain %145 %133 %30
+          %147 = OpLoad %7 %146 Aligned|Volatile 16
+          %148 = OpAccessChain %24 %12 %19
+                 OpStore %148 %147 Volatile
+                 OpReturn
+                 OpFunctionEnd
+    )";
+    ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
   }
-
-  // Not OK to remove Aligned
-  ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                   MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
-                   SpvMemoryAccessMaskNone, 0)
-                   .IsApplicable(context.get(), transformation_context));
-
-  // OK: leaves the mask as is
-  ASSERT_TRUE(TransformationSetMemoryOperandsMask(
-                  MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
-                  SpvMemoryAccessAlignedMask, 0)
-                  .IsApplicable(context.get(), transformation_context));
-
-  {
-    // OK: adds Nontemporal and Volatile
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
-        SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask |
-            SpvMemoryAccessVolatileMask,
-        0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
-
-  // Not OK to remove Volatile
-  ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                   MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
-                   SpvMemoryAccessNontemporalMask, 0)
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Not OK to add Aligned
-  ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                   MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
-                   SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0)
-                   .IsApplicable(context.get(), transformation_context));
-
-  {
-    // OK: adds Nontemporal
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
-        SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
-
-  {
-    // OK: adds Nontemporal (creates new operand)
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(21, SpvOpCopyMemory, 2),
-        SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
-
-  {
-    // OK: adds Nontemporal and Volatile
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
-        SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
-
-  {
-    // OK: removes Nontemporal, adds Volatile
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(148, SpvOpStore, 0),
-        SpvMemoryAccessVolatileMask, 0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
-
-  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 %7 "Point3D"
-               OpMemberName %7 0 "x"
-               OpMemberName %7 1 "y"
-               OpMemberName %7 2 "z"
-               OpName %12 "global_points"
-               OpName %15 "block"
-               OpMemberName %15 0 "in_points"
-               OpMemberName %15 1 "in_point"
-               OpName %17 ""
-               OpName %133 "local_points"
-               OpMemberDecorate %7 0 Offset 0
-               OpMemberDecorate %7 1 Offset 4
-               OpMemberDecorate %7 2 Offset 8
-               OpDecorate %10 ArrayStride 16
-               OpMemberDecorate %15 0 Offset 0
-               OpMemberDecorate %15 1 Offset 192
-               OpDecorate %15 Block
-               OpDecorate %17 DescriptorSet 0
-               OpDecorate %17 Binding 0
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypeStruct %6 %6 %6
-          %8 = OpTypeInt 32 0
-          %9 = OpConstant %8 12
-         %10 = OpTypeArray %7 %9
-         %11 = OpTypePointer Private %10
-         %12 = OpVariable %11 Private
-         %15 = OpTypeStruct %10 %7
-         %16 = OpTypePointer Uniform %15
-         %17 = OpVariable %16 Uniform
-         %18 = OpTypeInt 32 1
-         %19 = OpConstant %18 0
-         %20 = OpTypePointer Uniform %10
-         %24 = OpTypePointer Private %7
-         %27 = OpTypePointer Private %6
-         %30 = OpConstant %18 1
-        %132 = OpTypePointer Function %10
-        %135 = OpTypePointer Uniform %7
-        %145 = OpTypePointer Function %7
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-        %133 = OpVariable %132 Function
-         %21 = OpAccessChain %20 %17 %19
-               OpCopyMemory %12 %21 Aligned|Nontemporal|Volatile 16
-               OpCopyMemory %133 %12 Nontemporal|Volatile
-               OpCopyMemory %133 %12 Nontemporal|Volatile
-        %136 = OpAccessChain %135 %17 %30
-        %138 = OpAccessChain %24 %12 %19
-               OpCopyMemory %138 %136 Nontemporal|Volatile
-        %146 = OpAccessChain %145 %133 %30
-        %147 = OpLoad %7 %146 Aligned|Volatile 16
-        %148 = OpAccessChain %24 %12 %19
-               OpStore %148 %147 Volatile
-               OpReturn
-               OpFunctionEnd
-  )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
-TEST(TransformationSetMemoryOperandsMaskTest, Spirv14) {
+TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) {
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -339,180 +357,183 @@
                OpFunctionEnd
   )";
 
-  const auto env = SPV_ENV_UNIVERSAL_1_4;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  spvtools::ValidatorOptions validator_options;
-  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
-                                               kConsoleMessageConsumer));
-  TransformationContext transformation_context(
-      MakeUnique<FactManager>(context.get()), validator_options);
-  {
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
-        SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 1);
-    // Bad: cannot remove aligned
-    ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                     MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
-                     SpvMemoryAccessVolatileMask, 1)
-                     .IsApplicable(context.get(), transformation_context));
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+  for (auto env : {SPV_ENV_UNIVERSAL_1_4, SPV_ENV_UNIVERSAL_1_5,
+                   SPV_ENV_VULKAN_1_1_SPIRV_1_4, SPV_ENV_VULKAN_1_2}) {
+    const auto consumer = nullptr;
+    const auto context =
+        BuildModule(env, consumer, shader, kFuzzAssembleOption);
+    spvtools::ValidatorOptions validator_options;
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
+    {
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+          SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 1);
+      // Bad: cannot remove aligned
+      ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+                       MakeInstructionDescriptor(21, SpvOpCopyMemory, 0),
+                       SpvMemoryAccessVolatileMask, 1)
+                       .IsApplicable(context.get(), transformation_context));
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  {
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
-        SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 1);
-    // Bad: cannot remove volatile
-    ASSERT_FALSE(TransformationSetMemoryOperandsMask(
-                     MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
-                     SpvMemoryAccessNontemporalMask, 0)
-                     .IsApplicable(context.get(), transformation_context));
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+    {
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+          SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 1);
+      // Bad: cannot remove volatile
+      ASSERT_FALSE(TransformationSetMemoryOperandsMask(
+                       MakeInstructionDescriptor(21, SpvOpCopyMemory, 1),
+                       SpvMemoryAccessNontemporalMask, 0)
+                       .IsApplicable(context.get(), transformation_context));
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  {
-    // Creates the first operand.
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(21, SpvOpCopyMemory, 2),
-        SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+    {
+      // Creates the first operand.
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 2),
+          SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  {
-    // Creates both operands.
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(21, SpvOpCopyMemory, 3),
-        SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 1);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+    {
+      // Creates both operands.
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(21, SpvOpCopyMemory, 3),
+          SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 1);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  {
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
-        SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask, 1);
-    // Bad: the first mask is None, so Aligned cannot be added to it.
-    ASSERT_FALSE(
-        TransformationSetMemoryOperandsMask(
-            MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
-            SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask, 0)
-            .IsApplicable(context.get(), transformation_context));
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+    {
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
+          SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask, 1);
+      // Bad: the first mask is None, so Aligned cannot be added to it.
+      ASSERT_FALSE(
+          TransformationSetMemoryOperandsMask(
+              MakeInstructionDescriptor(138, SpvOpCopyMemory, 0),
+              SpvMemoryAccessAlignedMask | SpvMemoryAccessNontemporalMask, 0)
+              .IsApplicable(context.get(), transformation_context));
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  {
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(138, SpvOpCopyMemory, 1),
-        SpvMemoryAccessVolatileMask, 1);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+    {
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(138, SpvOpCopyMemory, 1),
+          SpvMemoryAccessVolatileMask, 1);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  {
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(147, SpvOpLoad, 0),
-        SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask, 0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+    {
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(147, SpvOpLoad, 0),
+          SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  {
-    TransformationSetMemoryOperandsMask transformation(
-        MakeInstructionDescriptor(148, SpvOpStore, 0), SpvMemoryAccessMaskNone,
-        0);
-    ASSERT_TRUE(
-        transformation.IsApplicable(context.get(), transformation_context));
-    ApplyAndCheckFreshIds(transformation, context.get(),
-                          &transformation_context);
-  }
+    {
+      TransformationSetMemoryOperandsMask transformation(
+          MakeInstructionDescriptor(148, SpvOpStore, 0),
+          SpvMemoryAccessMaskNone, 0);
+      ASSERT_TRUE(
+          transformation.IsApplicable(context.get(), transformation_context));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+    }
 
-  std::string after_transformation = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %12 %17
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %7 "Point3D"
-               OpMemberName %7 0 "x"
-               OpMemberName %7 1 "y"
-               OpMemberName %7 2 "z"
-               OpName %12 "global_points"
-               OpName %15 "block"
-               OpMemberName %15 0 "in_points"
-               OpMemberName %15 1 "in_point"
-               OpName %17 ""
-               OpName %133 "local_points"
-               OpMemberDecorate %7 0 Offset 0
-               OpMemberDecorate %7 1 Offset 4
-               OpMemberDecorate %7 2 Offset 8
-               OpDecorate %10 ArrayStride 16
-               OpMemberDecorate %15 0 Offset 0
-               OpMemberDecorate %15 1 Offset 192
-               OpDecorate %15 Block
-               OpDecorate %17 DescriptorSet 0
-               OpDecorate %17 Binding 0
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypeStruct %6 %6 %6
-          %8 = OpTypeInt 32 0
-          %9 = OpConstant %8 12
-         %10 = OpTypeArray %7 %9
-         %11 = OpTypePointer Private %10
-         %12 = OpVariable %11 Private
-         %15 = OpTypeStruct %10 %7
-         %16 = OpTypePointer Uniform %15
-         %17 = OpVariable %16 Uniform
-         %18 = OpTypeInt 32 1
-         %19 = OpConstant %18 0
-         %20 = OpTypePointer Uniform %10
-         %24 = OpTypePointer Private %7
-         %27 = OpTypePointer Private %6
-         %30 = OpConstant %18 1
-        %132 = OpTypePointer Function %10
-        %135 = OpTypePointer Uniform %7
-        %145 = OpTypePointer Function %7
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-        %133 = OpVariable %132 Function
-         %21 = OpAccessChain %20 %17 %19
-               OpCopyMemory %12 %21 Aligned 16 Aligned|Volatile 16
-               OpCopyMemory %133 %12 Volatile Nontemporal|Volatile
-               OpCopyMemory %133 %12 Nontemporal|Volatile
-               OpCopyMemory %133 %12 None Nontemporal|Volatile
-        %136 = OpAccessChain %135 %17 %30
-        %138 = OpAccessChain %24 %12 %19
-               OpCopyMemory %138 %136 None Aligned|Nontemporal 16
-               OpCopyMemory %138 %136 Aligned 16 Volatile
-        %146 = OpAccessChain %145 %133 %30
-        %147 = OpLoad %7 %146 Volatile|Aligned 16
-        %148 = OpAccessChain %24 %12 %19
-               OpStore %148 %147 None
-               OpReturn
-               OpFunctionEnd
-  )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+    std::string after_transformation = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %4 "main" %12 %17
+                 OpExecutionMode %4 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %4 "main"
+                 OpName %7 "Point3D"
+                 OpMemberName %7 0 "x"
+                 OpMemberName %7 1 "y"
+                 OpMemberName %7 2 "z"
+                 OpName %12 "global_points"
+                 OpName %15 "block"
+                 OpMemberName %15 0 "in_points"
+                 OpMemberName %15 1 "in_point"
+                 OpName %17 ""
+                 OpName %133 "local_points"
+                 OpMemberDecorate %7 0 Offset 0
+                 OpMemberDecorate %7 1 Offset 4
+                 OpMemberDecorate %7 2 Offset 8
+                 OpDecorate %10 ArrayStride 16
+                 OpMemberDecorate %15 0 Offset 0
+                 OpMemberDecorate %15 1 Offset 192
+                 OpDecorate %15 Block
+                 OpDecorate %17 DescriptorSet 0
+                 OpDecorate %17 Binding 0
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeStruct %6 %6 %6
+            %8 = OpTypeInt 32 0
+            %9 = OpConstant %8 12
+           %10 = OpTypeArray %7 %9
+           %11 = OpTypePointer Private %10
+           %12 = OpVariable %11 Private
+           %15 = OpTypeStruct %10 %7
+           %16 = OpTypePointer Uniform %15
+           %17 = OpVariable %16 Uniform
+           %18 = OpTypeInt 32 1
+           %19 = OpConstant %18 0
+           %20 = OpTypePointer Uniform %10
+           %24 = OpTypePointer Private %7
+           %27 = OpTypePointer Private %6
+           %30 = OpConstant %18 1
+          %132 = OpTypePointer Function %10
+          %135 = OpTypePointer Uniform %7
+          %145 = OpTypePointer Function %7
+            %4 = OpFunction %2 None %3
+            %5 = OpLabel
+          %133 = OpVariable %132 Function
+           %21 = OpAccessChain %20 %17 %19
+                 OpCopyMemory %12 %21 Aligned 16 Aligned|Volatile 16
+                 OpCopyMemory %133 %12 Volatile Nontemporal|Volatile
+                 OpCopyMemory %133 %12 Nontemporal|Volatile
+                 OpCopyMemory %133 %12 None Nontemporal|Volatile
+          %136 = OpAccessChain %135 %17 %30
+          %138 = OpAccessChain %24 %12 %19
+                 OpCopyMemory %138 %136 None Aligned|Nontemporal 16
+                 OpCopyMemory %138 %136 Aligned 16 Volatile
+          %146 = OpAccessChain %145 %133 %30
+          %147 = OpLoad %7 %146 Volatile|Aligned 16
+          %148 = OpAccessChain %24 %12 %19
+                 OpStore %148 %147 None
+                 OpReturn
+                 OpFunctionEnd
+    )";
+    ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+  }
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_store_test.cpp b/test/fuzz/transformation_store_test.cpp
index 93257d0..dd653e2 100644
--- a/test/fuzz/transformation_store_test.cpp
+++ b/test/fuzz/transformation_store_test.cpp
@@ -26,6 +26,7 @@
 TEST(TransformationStoreTest, BasicTest) {
   std::string shader = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %92 %52 %53
@@ -50,7 +51,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
          %80 = OpConstantComposite %8 %21 %24
@@ -148,90 +148,107 @@
   //  61 - undefined
 
   // Bad: attempt to store to 11 from outside its function
-  ASSERT_FALSE(TransformationStore(
-                   11, 80, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(11, false, 0, 0, 80,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: pointer is not available
-  ASSERT_FALSE(TransformationStore(
-                   81, 80, MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(81, false, 0, 0, 80,
+                          MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: attempt to insert before OpVariable
-  ASSERT_FALSE(TransformationStore(
-                   52, 24, MakeInstructionDescriptor(27, SpvOpVariable, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(52, false, 0, 0, 24,
+                          MakeInstructionDescriptor(27, SpvOpVariable, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: pointer id does not exist
-  ASSERT_FALSE(TransformationStore(
-                   1000, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(1000, false, 0, 0, 24,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: pointer id exists but does not have a type
-  ASSERT_FALSE(TransformationStore(
-                   5, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(5, false, 0, 0, 24,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: pointer id exists and has a type, but is not a pointer
-  ASSERT_FALSE(TransformationStore(
-                   24, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(24, false, 0, 0, 24,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: attempt to store to a null pointer
-  ASSERT_FALSE(TransformationStore(
-                   60, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(60, false, 0, 0, 24,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: attempt to store to an undefined pointer
-  ASSERT_FALSE(TransformationStore(
-                   61, 21, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(61, false, 0, 0, 21,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: %82 is not available at the program point
   ASSERT_FALSE(
-      TransformationStore(82, 80, MakeInstructionDescriptor(37, SpvOpReturn, 0))
+      TransformationStore(82, false, 0, 0, 80,
+                          MakeInstructionDescriptor(37, SpvOpReturn, 0))
           .IsApplicable(context.get(), transformation_context));
 
   // Bad: value id does not exist
-  ASSERT_FALSE(TransformationStore(
-                   27, 1000, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(27, false, 0, 0, 1000,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: value id exists but does not have a type
-  ASSERT_FALSE(TransformationStore(
-                   27, 15, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(27, false, 0, 0, 15,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: value id exists but has the wrong type
-  ASSERT_FALSE(TransformationStore(
-                   27, 14, MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(27, false, 0, 0, 14,
+                          MakeInstructionDescriptor(38, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: attempt to store to read-only variable
-  ASSERT_FALSE(TransformationStore(
-                   92, 93, MakeInstructionDescriptor(40, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(92, false, 0, 0, 93,
+                          MakeInstructionDescriptor(40, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: value is not available
-  ASSERT_FALSE(TransformationStore(
-                   27, 95, MakeInstructionDescriptor(40, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(27, false, 0, 0, 95,
+                          MakeInstructionDescriptor(40, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // Bad: variable being stored to does not have an irrelevant pointee value,
   // and the store is not in a dead block.
-  ASSERT_FALSE(TransformationStore(
-                   20, 95, MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(20, false, 0, 0, 95,
+                          MakeInstructionDescriptor(45, SpvOpCopyObject, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   // The described instruction does not exist.
-  ASSERT_FALSE(TransformationStore(
-                   27, 80, MakeInstructionDescriptor(1000, SpvOpAccessChain, 0))
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationStore(27, false, 0, 0, 80,
+                          MakeInstructionDescriptor(1000, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
 
   {
     // Store to irrelevant variable from dead block.
     TransformationStore transformation(
-        27, 80, MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
+        27, false, 0, 0, 80,
+        MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -243,7 +260,8 @@
   {
     // Store to irrelevant variable from live block.
     TransformationStore transformation(
-        11, 95, MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
+        11, false, 0, 0, 95,
+        MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -255,7 +273,8 @@
   {
     // Store to irrelevant variable from live block.
     TransformationStore transformation(
-        46, 80, MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
+        46, false, 0, 0, 80,
+        MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -267,7 +286,8 @@
   {
     // Store to irrelevant variable from live block.
     TransformationStore transformation(
-        16, 21, MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
+        16, false, 0, 0, 21,
+        MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -279,7 +299,8 @@
   {
     // Store to non-irrelevant variable from dead block.
     TransformationStore transformation(
-        53, 21, MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
+        53, false, 0, 0, 21,
+        MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     ApplyAndCheckFreshIds(transformation, context.get(),
@@ -290,6 +311,7 @@
 
   std::string after_transformation = R"(
                OpCapability Shader
+               OpCapability VariablePointers
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %4 "main" %92 %52 %53
@@ -314,7 +336,6 @@
          %34 = OpTypeBool
          %35 = OpConstantFalse %34
          %60 = OpConstantNull %50
-         %61 = OpUndef %51
          %52 = OpVariable %50 Private
          %53 = OpVariable %51 Private
          %80 = OpConstantComposite %8 %21 %24
@@ -414,16 +435,190 @@
   transformation_context.GetFactManager()->AddFactBlockIsDead(5);
 
   ASSERT_FALSE(
-      TransformationStore(15, 13, MakeInstructionDescriptor(27, SpvOpReturn, 0))
+      TransformationStore(15, false, 0, 0, 13,
+                          MakeInstructionDescriptor(27, SpvOpReturn, 0))
           .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
-      TransformationStore(19, 50, MakeInstructionDescriptor(27, SpvOpReturn, 0))
+      TransformationStore(19, false, 0, 0, 50,
+                          MakeInstructionDescriptor(27, SpvOpReturn, 0))
           .IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
-      TransformationStore(27, 50, MakeInstructionDescriptor(27, SpvOpReturn, 0))
+      TransformationStore(27, false, 0, 0, 50,
+                          MakeInstructionDescriptor(27, SpvOpReturn, 0))
           .IsApplicable(context.get(), transformation_context));
 }
 
+TEST(TransformationStoreTest, SupportAtomicStore) {
+  const std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int8
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 8 1
+          %9 = OpTypeInt 32 0
+         %26 = OpTypeFloat 32
+          %8 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %8
+         %11 = OpVariable %10 StorageBuffer
+         %19 = OpConstant %26 0
+         %18 = OpConstant %9 1
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 4
+         %16 = OpConstant %6 7
+         %17 = OpConstant %7 4
+         %20 = OpConstant %9 64
+         %21 = OpConstant %6 15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpAccessChain %13 %11 %12
+         %24 = OpAccessChain %13 %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      14);
+
+  // Bad: id 100 of memory scope instruction does not exist.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 100, 20, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: id 100 of memory semantics instruction does not exist.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 100, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: memory scope should be |OpConstant| opcode.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 5, 20, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: memory semantics should be |OpConstant| opcode.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 5, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: The memory scope instruction must have an Integer operand.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 19, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: The memory memory semantics instruction must have an Integer operand.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 19, 20, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Integer size of the memory scope must be equal to 32 bits.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 17, 20, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Integer size of memory semantics must be equal to 32 bits.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 17, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: memory scope value must be 4 (SpvScopeInvocation).
+  ASSERT_FALSE(
+      TransformationStore(14, true, 16, 20, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: memory semantics value must be either:
+  // 64 (SpvMemorySemanticsUniformMemoryMask)
+  // 256 (SpvMemorySemanticsWorkgroupMemoryMask)
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 16, 21,
+                          MakeInstructionDescriptor(24, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: The described instruction does not exist
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 20, 21,
+                          MakeInstructionDescriptor(150, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Can't insert OpAccessChain before the id 15 of memory scope.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 20, 21,
+                          MakeInstructionDescriptor(15, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Can't insert OpAccessChain before the id 20 of memory semantics.
+  ASSERT_FALSE(
+      TransformationStore(14, true, 15, 20, 21,
+                          MakeInstructionDescriptor(20, SpvOpAccessChain, 0))
+          .IsApplicable(context.get(), transformation_context));
+
+  // Successful transformations.
+  {
+    TransformationStore transformation(
+        14, true, 15, 20, 21, MakeInstructionDescriptor(24, SpvOpReturn, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  const std::string after_transformation = R"(
+               OpCapability Shader
+               OpCapability Int8
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 8 1
+          %9 = OpTypeInt 32 0
+         %26 = OpTypeFloat 32
+          %8 = OpTypeStruct %6
+         %10 = OpTypePointer StorageBuffer %8
+         %11 = OpVariable %10 StorageBuffer
+         %19 = OpConstant %26 0
+         %18 = OpConstant %9 1
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer StorageBuffer %6
+         %15 = OpConstant %6 4
+         %16 = OpConstant %6 7
+         %17 = OpConstant %7 4
+         %20 = OpConstant %9 64
+         %21 = OpConstant %6 15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpAccessChain %13 %11 %12
+         %24 = OpAccessChain %13 %11 %12
+               OpAtomicStore %14 %15 %20 %21
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp b/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp
index e7a8732..6133a7a 100644
--- a/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp
+++ b/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp
@@ -91,9 +91,31 @@
 
   TransformationSwapConditionalBranchOperands transformation(
       MakeInstructionDescriptor(15, SpvOpBranchConditional, 0), 26);
+  ASSERT_EQ(nullptr, context->get_def_use_mgr()->GetDef(26));
+  ASSERT_EQ(nullptr, context->get_instr_block(26));
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
   ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_EQ(SpvOpLogicalNot, context->get_def_use_mgr()->GetDef(26)->opcode());
+  ASSERT_EQ(5, context->get_instr_block(26)->id());
+  ASSERT_EQ(1, context->get_def_use_mgr()->NumUses(26));
+
+  // Check that the def-use manager knows that the conditional branch operands
+  // have been swapped.
+  std::vector<std::pair<uint32_t, uint32_t>> phi_operand_to_new_operand_index =
+      {{16, 2}, {21, 1}};
+  for (std::pair<uint32_t, uint32_t>& entry :
+       phi_operand_to_new_operand_index) {
+    context->get_def_use_mgr()->WhileEachUse(
+        entry.first,
+        [&entry](opt::Instruction* inst, uint32_t operand_index) -> bool {
+          if (inst->opcode() == SpvOpBranchConditional) {
+            EXPECT_EQ(entry.second, operand_index);
+            return false;
+          }
+          return true;
+        });
+  }
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_swap_function_variables_test.cpp b/test/fuzz/transformation_swap_function_variables_test.cpp
new file mode 100644
index 0000000..8132aa4
--- /dev/null
+++ b/test/fuzz/transformation_swap_function_variables_test.cpp
@@ -0,0 +1,288 @@
+// Copyright (c) 2021 Mostafa Ashraf
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_swap_function_variables.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSwapFunctionVariables, NotApplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeVector %8 2
+         %11 = OpTypePointer Function %10
+         %12 = OpTypeVector %8 3
+         %13 = OpTypeMatrix %12 3
+         %14 = OpTypePointer Function %13
+         %15 = OpTypeFunction %2 %7 %9 %11 %14 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %9 Function
+         %26 = OpVariable %11 Function
+         %27 = OpVariable %14 Function
+         %28 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+         %30 = OpVariable %7 Function
+         %32 = OpVariable %9 Function
+         %34 = OpVariable %11 Function
+         %36 = OpVariable %14 Function
+         %38 = OpVariable %7 Function
+         %40 = OpVariable %7 Function
+         %31 = OpLoad %6 %24
+               OpStore %30 %31
+         %33 = OpLoad %8 %25
+               OpStore %32 %33
+         %35 = OpLoad %10 %26
+               OpStore %34 %35
+         %37 = OpLoad %13 %27
+               OpStore %36 %37
+         %39 = OpLoad %6 %28
+               OpStore %38 %39
+         %41 = OpLoad %6 %29
+               OpStore %40 %41
+         %42 = OpFunctionCall %2 %22 %30 %32 %34 %36 %38 %40
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %2 None %15
+         %16 = OpFunctionParameter %7
+         %17 = OpFunctionParameter %9
+         %18 = OpFunctionParameter %11
+         %19 = OpFunctionParameter %14
+         %20 = OpFunctionParameter %7
+         %21 = OpFunctionParameter %7
+         %23 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+#ifndef NDEBUG
+  // Can't swap variable with itself.
+  ASSERT_DEATH(TransformationSwapFunctionVariables(7, 7).IsApplicable(
+                   context.get(), transformation_context),
+               "Two results ids are equal");
+#endif
+
+  // Invalid because 200 is not the id of an instruction.
+  ASSERT_FALSE(TransformationSwapFunctionVariables(1, 200).IsApplicable(
+      context.get(), transformation_context));
+  // Invalid because 5 is not the id of an instruction.
+  ASSERT_FALSE(TransformationSwapFunctionVariables(5, 24).IsApplicable(
+      context.get(), transformation_context));
+  // Can't swap two instructions from two different blocks.
+  ASSERT_FALSE(TransformationSwapFunctionVariables(16, 26).IsApplicable(
+      context.get(), transformation_context));
+}
+
+TEST(TransformationSwapFunctionVariables, IsApplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeVector %8 2
+         %11 = OpTypePointer Function %10
+         %12 = OpTypeVector %8 3
+         %13 = OpTypeMatrix %12 3
+         %14 = OpTypePointer Function %13
+         %15 = OpTypeFunction %2 %7 %9 %11 %14 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %9 Function
+         %26 = OpVariable %11 Function
+         %27 = OpVariable %14 Function
+         %28 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+         %30 = OpVariable %7 Function
+         %32 = OpVariable %9 Function
+         %34 = OpVariable %11 Function
+         %36 = OpVariable %14 Function
+         %38 = OpVariable %7 Function
+         %40 = OpVariable %7 Function
+         %31 = OpLoad %6 %24
+               OpStore %30 %31
+         %33 = OpLoad %8 %25
+               OpStore %32 %33
+         %35 = OpLoad %10 %26
+               OpStore %34 %35
+         %37 = OpLoad %13 %27
+               OpStore %36 %37
+         %39 = OpLoad %6 %28
+               OpStore %38 %39
+         %41 = OpLoad %6 %29
+               OpStore %40 %41
+         %42 = OpFunctionCall %2 %22 %30 %32 %34 %36 %38 %40
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %2 None %15
+         %16 = OpFunctionParameter %7
+         %17 = OpFunctionParameter %9
+         %18 = OpFunctionParameter %11
+         %19 = OpFunctionParameter %14
+         %20 = OpFunctionParameter %7
+         %21 = OpFunctionParameter %7
+         %23 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  // Get Unique pointer of IRContext.
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Successful transformations
+  {
+    auto first_instruction = context->get_def_use_mgr()->GetDef(24);
+    auto second_instruction = context->get_def_use_mgr()->GetDef(28);
+    // Swap two OpVariable instructions in the same function.
+    TransformationSwapFunctionVariables transformation(24, 28);
+
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+
+    ASSERT_EQ(first_instruction, context->get_def_use_mgr()->GetDef(24));
+    ASSERT_EQ(second_instruction, context->get_def_use_mgr()->GetDef(28));
+  }
+  {
+    auto first_instruction = context->get_def_use_mgr()->GetDef(38);
+    auto second_instruction = context->get_def_use_mgr()->GetDef(40);
+    // Swap two OpVariable instructions in the same function.
+    TransformationSwapFunctionVariables transformation(38, 40);
+
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+
+    ASSERT_EQ(first_instruction, context->get_def_use_mgr()->GetDef(38));
+    ASSERT_EQ(second_instruction, context->get_def_use_mgr()->GetDef(40));
+  }
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFloat 32
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeVector %8 2
+         %11 = OpTypePointer Function %10
+         %12 = OpTypeVector %8 3
+         %13 = OpTypeMatrix %12 3
+         %14 = OpTypePointer Function %13
+         %15 = OpTypeFunction %2 %7 %9 %11 %14 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %28 = OpVariable %7 Function
+         %25 = OpVariable %9 Function
+         %26 = OpVariable %11 Function
+         %27 = OpVariable %14 Function
+         %24 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+         %30 = OpVariable %7 Function
+         %32 = OpVariable %9 Function
+         %34 = OpVariable %11 Function
+         %36 = OpVariable %14 Function
+         %40 = OpVariable %7 Function
+         %38 = OpVariable %7 Function
+         %31 = OpLoad %6 %24
+               OpStore %30 %31
+         %33 = OpLoad %8 %25
+               OpStore %32 %33
+         %35 = OpLoad %10 %26
+               OpStore %34 %35
+         %37 = OpLoad %13 %27
+               OpStore %36 %37
+         %39 = OpLoad %6 %28
+               OpStore %38 %39
+         %41 = OpLoad %6 %29
+               OpStore %40 %41
+         %42 = OpFunctionCall %2 %22 %30 %32 %34 %36 %38 %40
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %2 None %15
+         %16 = OpFunctionParameter %7
+         %17 = OpFunctionParameter %9
+         %18 = OpFunctionParameter %11
+         %19 = OpFunctionParameter %14
+         %20 = OpFunctionParameter %7
+         %21 = OpFunctionParameter %7
+         %23 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_swap_two_functions_test.cpp b/test/fuzz/transformation_swap_two_functions_test.cpp
new file mode 100644
index 0000000..2034252
--- /dev/null
+++ b/test/fuzz/transformation_swap_two_functions_test.cpp
@@ -0,0 +1,257 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_swap_two_functions.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationSwapTwoFunctionsTest, SimpleTest) {
+  //   float multiplyBy2(in float value) {
+  //     return value*2.0;
+  //   }
+
+  //   float multiplyBy4(in float value) {
+  //     return multiplyBy2(value)*2.0;
+  //   }
+
+  //   float multiplyBy8(in float value) {
+  //     return multiplyBy2(value)*multiplyBy4(value);
+  //   }
+
+  //   layout(location=0) in float value;
+  //   void main() { //4
+  //     multiplyBy2(3.7); //10
+  //     multiplyBy4(3.9); //13
+  //     multiplyBy8(5.0); //16
+  //   }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %48
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "multiplyBy2(f1;"
+               OpName %9 "value"
+               OpName %13 "multiplyBy4(f1;"
+               OpName %12 "value"
+               OpName %16 "multiplyBy8(f1;"
+               OpName %15 "value"
+               OpName %23 "param"
+               OpName %29 "param"
+               OpName %32 "param"
+               OpName %39 "param"
+               OpName %42 "param"
+               OpName %45 "param"
+               OpName %48 "value"
+               OpDecorate %48 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %19 = OpConstant %6 2
+         %38 = OpConstant %6 3.70000005
+         %41 = OpConstant %6 3.9000001
+         %44 = OpConstant %6 5
+         %47 = OpTypePointer Input %6
+         %48 = OpVariable %47 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %39 = OpVariable %7 Function
+         %42 = OpVariable %7 Function
+         %45 = OpVariable %7 Function
+               OpStore %39 %38
+         %40 = OpFunctionCall %6 %10 %39
+               OpStore %42 %41
+         %43 = OpFunctionCall %6 %13 %42
+               OpStore %45 %44
+         %46 = OpFunctionCall %6 %16 %45
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %18 = OpLoad %6 %9
+         %20 = OpFMul %6 %18 %19
+               OpReturnValue %20
+               OpFunctionEnd
+         %13 = OpFunction %6 None %8
+         %12 = OpFunctionParameter %7
+         %14 = OpLabel
+         %23 = OpVariable %7 Function
+         %24 = OpLoad %6 %12
+               OpStore %23 %24
+         %25 = OpFunctionCall %6 %10 %23
+         %26 = OpFMul %6 %25 %19
+               OpReturnValue %26
+               OpFunctionEnd
+         %16 = OpFunction %6 None %8
+         %15 = OpFunctionParameter %7
+         %17 = OpLabel
+         %29 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %30 = OpLoad %6 %15
+               OpStore %29 %30
+         %31 = OpFunctionCall %6 %10 %29
+         %33 = OpLoad %6 %15
+               OpStore %32 %33
+         %34 = OpFunctionCall %6 %13 %32
+         %35 = OpFMul %6 %31 %34
+               OpReturnValue %35
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  // Check context validity.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+#ifndef NDEBUG
+  // Function should not swap with itself.
+  ASSERT_DEATH(TransformationSwapTwoFunctions(4, 4).IsApplicable(
+                   context.get(), transformation_context),
+               "The two function ids cannot be the same.");
+#endif
+
+  // Function with id 29 does not exist.
+  ASSERT_FALSE(TransformationSwapTwoFunctions(10, 29).IsApplicable(
+      context.get(), transformation_context));
+
+  // Function with id 30 does not exist.
+  ASSERT_FALSE(TransformationSwapTwoFunctions(30, 13).IsApplicable(
+      context.get(), transformation_context));
+
+  // Both functions with id 5 and 6 do not exist.
+  ASSERT_FALSE(TransformationSwapTwoFunctions(5, 6).IsApplicable(
+      context.get(), transformation_context));
+
+  // Function with result_id 10 and 13 should swap successfully.
+  auto swap_test5 = TransformationSwapTwoFunctions(10, 13);
+  ASSERT_TRUE(swap_test5.IsApplicable(context.get(), transformation_context));
+
+  // Get the definitions of functions 10 and 13, as recorded by the def-use
+  // manager.
+  auto def_use_manager = context->get_def_use_mgr();
+  auto function_10_inst = def_use_manager->GetDef(10);
+  auto function_13_inst = def_use_manager->GetDef(13);
+
+  ApplyAndCheckFreshIds(swap_test5, context.get(), &transformation_context);
+
+  // Check that def-use information for functions 10 and 13 has been preserved
+  // by the transformation.
+  ASSERT_EQ(function_10_inst, def_use_manager->GetDef(10));
+  ASSERT_EQ(function_13_inst, def_use_manager->GetDef(13));
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %48
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "multiplyBy2(f1;"
+               OpName %9 "value"
+               OpName %13 "multiplyBy4(f1;"
+               OpName %12 "value"
+               OpName %16 "multiplyBy8(f1;"
+               OpName %15 "value"
+               OpName %23 "param"
+               OpName %29 "param"
+               OpName %32 "param"
+               OpName %39 "param"
+               OpName %42 "param"
+               OpName %45 "param"
+               OpName %48 "value"
+               OpDecorate %48 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %19 = OpConstant %6 2
+         %38 = OpConstant %6 3.70000005
+         %41 = OpConstant %6 3.9000001
+         %44 = OpConstant %6 5
+         %47 = OpTypePointer Input %6
+         %48 = OpVariable %47 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %39 = OpVariable %7 Function
+         %42 = OpVariable %7 Function
+         %45 = OpVariable %7 Function
+               OpStore %39 %38
+         %40 = OpFunctionCall %6 %10 %39
+               OpStore %42 %41
+         %43 = OpFunctionCall %6 %13 %42
+               OpStore %45 %44
+         %46 = OpFunctionCall %6 %16 %45
+               OpReturn
+               OpFunctionEnd
+         %13 = OpFunction %6 None %8
+         %12 = OpFunctionParameter %7
+         %14 = OpLabel
+         %23 = OpVariable %7 Function
+         %24 = OpLoad %6 %12
+               OpStore %23 %24
+         %25 = OpFunctionCall %6 %10 %23
+         %26 = OpFMul %6 %25 %19
+               OpReturnValue %26
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+         %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %18 = OpLoad %6 %9
+         %20 = OpFMul %6 %18 %19
+               OpReturnValue %20
+               OpFunctionEnd
+         %16 = OpFunction %6 None %8
+         %15 = OpFunctionParameter %7
+         %17 = OpLabel
+         %29 = OpVariable %7 Function
+         %32 = OpVariable %7 Function
+         %30 = OpLoad %6 %15
+               OpStore %29 %30
+         %31 = OpFunctionCall %6 %10 %29
+         %33 = OpLoad %6 %15
+               OpStore %32 %33
+         %34 = OpFunctionCall %6 %13 %32
+         %35 = OpFMul %6 %31 %34
+               OpReturnValue %35
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_wrap_vector_synonym_test.cpp b/test/fuzz/transformation_wrap_vector_synonym_test.cpp
new file mode 100644
index 0000000..0d1009b
--- /dev/null
+++ b/test/fuzz/transformation_wrap_vector_synonym_test.cpp
@@ -0,0 +1,1553 @@
+// Copyright (c) 2021 Shiyu Liu
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_wrap_vector_synonym.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationWrapVectorSynonym, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %97
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 -5
+         %12 = OpTypeVector %6 2
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeInt 32 0
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 8
+         %23 = OpConstant %18 2
+         %24 = OpTypeVector %18 3
+         %25 = OpTypePointer Function %24
+         %31 = OpTypeFloat 32
+         %32 = OpTypePointer Function %31
+         %34 = OpConstant %31 3.29999995
+         %36 = OpConstant %31 1.10000002
+         %37 = OpTypeVector %31 4
+         %38 = OpTypePointer Function %37
+         %96 = OpTypePointer Input %31
+         %97 = OpVariable %96 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %33 = OpVariable %32 Function
+         %35 = OpVariable %32 Function
+         %39 = OpVariable %38 Function
+         %47 = OpVariable %7 Function
+         %51 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %59 = OpVariable %7 Function
+         %63 = OpVariable %19 Function
+         %67 = OpVariable %19 Function
+         %71 = OpVariable %19 Function
+         %75 = OpVariable %19 Function
+         %79 = OpVariable %32 Function
+         %83 = OpVariable %32 Function
+         %87 = OpVariable %32 Function
+         %91 = OpVariable %32 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+               OpStore %20 %21
+               OpStore %22 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %22
+         %30 = OpCompositeConstruct %24 %27 %28 %29
+               OpStore %26 %30
+               OpStore %33 %34
+               OpStore %35 %36
+         %40 = OpLoad %31 %33
+         %41 = OpLoad %31 %33
+         %42 = OpLoad %31 %35
+         %43 = OpLoad %31 %35
+         %44 = OpCompositeConstruct %37 %40 %41 %42 %43
+         %45 = OpLoad %37 %39
+         %46 = OpVectorShuffle %37 %45 %44 5 6 7 4
+               OpStore %39 %46
+         %48 = OpLoad %6 %8
+         %49 = OpLoad %6 %10
+        %100 = OpCompositeConstruct %12 %48 %48
+        %101 = OpCompositeConstruct %12 %49 %49
+         %50 = OpIAdd %6 %48 %49
+               OpStore %47 %50
+         %52 = OpLoad %6 %8
+         %53 = OpLoad %6 %10
+         %54 = OpISub %6 %52 %53
+               OpStore %51 %54
+         %56 = OpLoad %6 %8
+         %57 = OpLoad %6 %10
+         %58 = OpIMul %6 %56 %57
+               OpStore %55 %58
+         %60 = OpLoad %6 %8
+         %61 = OpLoad %6 %10
+         %62 = OpSDiv %6 %60 %61
+               OpStore %59 %62
+         %64 = OpLoad %18 %20
+         %65 = OpLoad %18 %22
+         %66 = OpIAdd %18 %64 %65
+               OpStore %63 %66
+         %68 = OpLoad %18 %20
+         %69 = OpLoad %18 %22
+         %70 = OpISub %18 %68 %69
+               OpStore %67 %70
+         %72 = OpLoad %18 %20
+         %73 = OpLoad %18 %22
+         %74 = OpIMul %18 %72 %73
+               OpStore %71 %74
+         %76 = OpLoad %18 %20
+         %77 = OpLoad %18 %22
+         %78 = OpUDiv %18 %76 %77
+               OpStore %75 %78
+         %80 = OpLoad %31 %33
+         %81 = OpLoad %31 %35
+         %82 = OpFAdd %31 %80 %81
+               OpStore %79 %82
+         %84 = OpLoad %31 %33
+         %85 = OpLoad %31 %35
+         %86 = OpFSub %31 %84 %85
+               OpStore %83 %86
+         %88 = OpLoad %31 %33
+         %89 = OpLoad %31 %35
+         %90 = OpFMul %31 %88 %89
+               OpStore %87 %90
+         %92 = OpLoad %31 %33
+         %93 = OpLoad %31 %35
+         %94 = OpFDiv %31 %92 %93
+               OpStore %91 %94
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  // Check context validity.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Vec Type Id |   Vector Type  |  Element Type id |   Element Type  |
+  // ------------+----------------+------------------+-----------------+
+  //     12      |      vec2      |         6        |      int32      |
+  //     24      |      vec3      |        18        |     uint32      |
+  //     37      |      vec4      |        31        |      float      |
+
+  // Instruction Id | Opcode  | Type Id | constant id 1 | constant id 2 |
+  // ---------------+---------+---------+---------------+---------------+
+  //       50       | OpIAdd  |    6    |      48       |      49       |
+  //       54       | OpISub  |    6    |      52       |      53       |
+  //       58       | OpIMul  |    6    |      56       |      57       |
+  //       62       | OpSDiv  |    6    |      60       |      61       |
+  //       66       | OpIAdd  |    18   |      64       |      65       |
+  //       70       | OpISub  |    18   |      68       |      69       |
+  //       74       | OpIMul  |    18   |      72       |      73       |
+  //       78       | OpUDiv  |    18   |      76       |      77       |
+  //       82       | OpFAdd  |    31   |      80       |      81       |
+  //       86       | OpFSub  |    31   |      84       |      85       |
+  //       90       | OpFMul  |    31   |      88       |      89       |
+  //       94       | OpFDiv  |    31   |      92       |      93       |
+
+  // Assert that the target scalar instruction result id is relevant.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(50));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(100, {1}), MakeDataDescriptor(48, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(101, {1}), MakeDataDescriptor(49, {}));
+
+  // The following are all invalid use.
+  {
+    // Bad: Instruction id does not exist.
+    TransformationWrapVectorSynonym wrap_add_int_bad1(103, 100, 101, 102, 1);
+    ASSERT_FALSE(
+        wrap_add_int_bad1.IsApplicable(context.get(), transformation_context));
+
+    // Bad: Instruction id given is not of a valid arithmetic operation typed
+    // instruction.
+    TransformationWrapVectorSynonym wrap_add_int_bad2(80, 100, 101, 102, 1);
+    ASSERT_FALSE(
+        wrap_add_int_bad1.IsApplicable(context.get(), transformation_context));
+
+    // Bad: the id for the first vector does not exist.
+    TransformationWrapVectorSynonym wrap_add_int_bad3(50, 105, 101, 102, 1);
+    ASSERT_FALSE(
+        wrap_add_int_bad3.IsApplicable(context.get(), transformation_context));
+
+    // Bad: the id for the second vector does not exist.
+    TransformationWrapVectorSynonym wrap_add_int_bad4(50, 100, 105, 102, 1);
+    ASSERT_FALSE(
+        wrap_add_int_bad4.IsApplicable(context.get(), transformation_context));
+
+    // Bad: vector id is not fresh.
+    TransformationWrapVectorSynonym wrap_add_int_bad6(50, 100, 101, 94, 1);
+    ASSERT_FALSE(
+        wrap_add_int_bad6.IsApplicable(context.get(), transformation_context));
+
+    // Bad: The position goes out of bound for the given vector type.
+    TransformationWrapVectorSynonym wrap_add_int_bad8(50, 100, 101, 102, 2);
+    ASSERT_FALSE(
+        wrap_add_int_bad8.IsApplicable(context.get(), transformation_context));
+
+    // Bad: The original instruction is not a valid scalar operation
+    // instruction.
+    TransformationWrapVectorSynonym wrap_add_int(27, 100, 101, 102, 1);
+    ASSERT_FALSE(
+        wrap_add_int.IsApplicable(context.get(), transformation_context));
+  }
+
+  // Good: The following transformation should be applicable.
+  TransformationWrapVectorSynonym wrap_add_int(50, 100, 101, 102, 1);
+  ASSERT_TRUE(wrap_add_int.IsApplicable(context.get(), transformation_context));
+  // Insert an arithmetic instruction of the same type to add two vectors.
+  ApplyAndCheckFreshIds(wrap_add_int, context.get(), &transformation_context);
+
+  // |instruction_id| and id at |scalar_position of the result vector should be
+  // synonyms.
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(102, {1}), MakeDataDescriptor(50, {})));
+
+  // After applying transformations, the instruction:
+  //
+  // %102 = OpIAdd %12 %100 %101
+  //
+  // should be added before:
+  //
+  // %50 = OpIAdd %6 %48 %49
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %97
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 -5
+         %12 = OpTypeVector %6 2
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeInt 32 0
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 8
+         %23 = OpConstant %18 2
+         %24 = OpTypeVector %18 3
+         %25 = OpTypePointer Function %24
+         %31 = OpTypeFloat 32
+         %32 = OpTypePointer Function %31
+         %34 = OpConstant %31 3.29999995
+         %36 = OpConstant %31 1.10000002
+         %37 = OpTypeVector %31 4
+         %38 = OpTypePointer Function %37
+         %96 = OpTypePointer Input %31
+         %97 = OpVariable %96 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %33 = OpVariable %32 Function
+         %35 = OpVariable %32 Function
+         %39 = OpVariable %38 Function
+         %47 = OpVariable %7 Function
+         %51 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %59 = OpVariable %7 Function
+         %63 = OpVariable %19 Function
+         %67 = OpVariable %19 Function
+         %71 = OpVariable %19 Function
+         %75 = OpVariable %19 Function
+         %79 = OpVariable %32 Function
+         %83 = OpVariable %32 Function
+         %87 = OpVariable %32 Function
+         %91 = OpVariable %32 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+               OpStore %20 %21
+               OpStore %22 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %22
+         %30 = OpCompositeConstruct %24 %27 %28 %29
+               OpStore %26 %30
+               OpStore %33 %34
+               OpStore %35 %36
+         %40 = OpLoad %31 %33
+         %41 = OpLoad %31 %33
+         %42 = OpLoad %31 %35
+         %43 = OpLoad %31 %35
+         %44 = OpCompositeConstruct %37 %40 %41 %42 %43
+         %45 = OpLoad %37 %39
+         %46 = OpVectorShuffle %37 %45 %44 5 6 7 4
+               OpStore %39 %46
+         %48 = OpLoad %6 %8
+         %49 = OpLoad %6 %10
+        %100 = OpCompositeConstruct %12 %48 %48
+        %101 = OpCompositeConstruct %12 %49 %49
+        %102 = OpIAdd %12 %100 %101
+         %50 = OpIAdd %6 %48 %49
+               OpStore %47 %50
+         %52 = OpLoad %6 %8
+         %53 = OpLoad %6 %10
+         %54 = OpISub %6 %52 %53
+               OpStore %51 %54
+         %56 = OpLoad %6 %8
+         %57 = OpLoad %6 %10
+         %58 = OpIMul %6 %56 %57
+               OpStore %55 %58
+         %60 = OpLoad %6 %8
+         %61 = OpLoad %6 %10
+         %62 = OpSDiv %6 %60 %61
+               OpStore %59 %62
+         %64 = OpLoad %18 %20
+         %65 = OpLoad %18 %22
+         %66 = OpIAdd %18 %64 %65
+               OpStore %63 %66
+         %68 = OpLoad %18 %20
+         %69 = OpLoad %18 %22
+         %70 = OpISub %18 %68 %69
+               OpStore %67 %70
+         %72 = OpLoad %18 %20
+         %73 = OpLoad %18 %22
+         %74 = OpIMul %18 %72 %73
+               OpStore %71 %74
+         %76 = OpLoad %18 %20
+         %77 = OpLoad %18 %22
+         %78 = OpUDiv %18 %76 %77
+               OpStore %75 %78
+         %80 = OpLoad %31 %33
+         %81 = OpLoad %31 %35
+         %82 = OpFAdd %31 %80 %81
+               OpStore %79 %82
+         %84 = OpLoad %31 %33
+         %85 = OpLoad %31 %35
+         %86 = OpFSub %31 %84 %85
+               OpStore %83 %86
+         %88 = OpLoad %31 %33
+         %89 = OpLoad %31 %35
+         %90 = OpFMul %31 %88 %89
+               OpStore %87 %90
+         %92 = OpLoad %31 %33
+         %93 = OpLoad %31 %35
+         %94 = OpFDiv %31 %92 %93
+               OpStore %91 %94
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationWrapVectorSynonym, OperationSupportTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %97
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 -5
+         %12 = OpTypeVector %6 2
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeInt 32 0
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 8
+         %23 = OpConstant %18 2
+         %24 = OpTypeVector %18 3
+         %25 = OpTypePointer Function %24
+         %31 = OpTypeFloat 32
+         %32 = OpTypePointer Function %31
+         %34 = OpConstant %31 3.29999995
+         %36 = OpConstant %31 1.10000002
+         %37 = OpTypeVector %31 4
+         %38 = OpTypePointer Function %37
+         %96 = OpTypePointer Input %31
+         %97 = OpVariable %96 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %33 = OpVariable %32 Function
+         %35 = OpVariable %32 Function
+         %39 = OpVariable %38 Function
+         %47 = OpVariable %7 Function
+         %51 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %59 = OpVariable %7 Function
+         %63 = OpVariable %19 Function
+         %67 = OpVariable %19 Function
+         %71 = OpVariable %19 Function
+         %75 = OpVariable %19 Function
+         %79 = OpVariable %32 Function
+         %83 = OpVariable %32 Function
+         %87 = OpVariable %32 Function
+         %91 = OpVariable %32 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+               OpStore %20 %21
+               OpStore %22 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %22
+         %30 = OpCompositeConstruct %24 %27 %28 %29
+               OpStore %26 %30
+               OpStore %33 %34
+               OpStore %35 %36
+         %40 = OpLoad %31 %33
+         %41 = OpLoad %31 %33
+         %42 = OpLoad %31 %35
+         %43 = OpLoad %31 %35
+         %44 = OpCompositeConstruct %37 %40 %41 %42 %43
+         %45 = OpLoad %37 %39
+         %46 = OpVectorShuffle %37 %45 %44 5 6 7 4
+               OpStore %39 %46
+         %48 = OpLoad %6 %8
+         %49 = OpLoad %6 %10
+         %50 = OpIAdd %6 %48 %49
+               OpStore %47 %50
+         %52 = OpLoad %6 %8
+         %53 = OpLoad %6 %10
+        %100 = OpCompositeConstruct %12 %52 %52
+        %101 = OpCompositeConstruct %12 %53 %53
+         %54 = OpISub %6 %52 %53
+               OpStore %51 %54
+         %56 = OpLoad %6 %8
+         %57 = OpLoad %6 %10
+        %103 = OpCompositeConstruct %12 %56 %56
+        %104 = OpCompositeConstruct %12 %57 %57
+         %58 = OpIMul %6 %56 %57
+               OpStore %55 %58
+         %60 = OpLoad %6 %8
+         %61 = OpLoad %6 %10
+         %62 = OpSDiv %6 %60 %61
+               OpStore %59 %62
+         %64 = OpLoad %18 %20
+         %65 = OpLoad %18 %22
+        %106 = OpCompositeConstruct %24 %64 %64 %64
+        %107 = OpCompositeConstruct %24 %65 %65 %65
+         %66 = OpIAdd %18 %64 %65
+               OpStore %63 %66
+         %68 = OpLoad %18 %20
+         %69 = OpLoad %18 %22
+        %109 = OpCompositeConstruct %24 %68 %68 %68
+        %110 = OpCompositeConstruct %24 %69 %69 %69
+         %70 = OpISub %18 %68 %69
+               OpStore %67 %70
+         %72 = OpLoad %18 %20
+         %73 = OpLoad %18 %22
+        %112 = OpCompositeConstruct %24 %72 %72 %72
+        %113 = OpCompositeConstruct %24 %73 %73 %73
+         %74 = OpIMul %18 %72 %73
+               OpStore %71 %74
+         %76 = OpLoad %18 %20
+         %77 = OpLoad %18 %22
+         %78 = OpUDiv %18 %76 %77
+               OpStore %75 %78
+         %80 = OpLoad %31 %33
+         %81 = OpLoad %31 %35
+        %115 = OpCompositeConstruct %37 %80 %80 %80 %80
+        %116 = OpCompositeConstruct %37 %81 %81 %81 %81
+         %82 = OpFAdd %31 %80 %81
+               OpStore %79 %82
+         %84 = OpLoad %31 %33
+         %85 = OpLoad %31 %35
+        %118 = OpCompositeConstruct %37 %84 %84 %84 %84
+        %119 = OpCompositeConstruct %37 %85 %85 %85 %85
+         %86 = OpFSub %31 %84 %85
+               OpStore %83 %86
+         %88 = OpLoad %31 %33
+         %89 = OpLoad %31 %35
+        %121 = OpCompositeConstruct %37 %88 %88 %88 %88
+        %122 = OpCompositeConstruct %37 %89 %89 %89 %89
+         %90 = OpFMul %31 %88 %89
+               OpStore %87 %90
+         %92 = OpLoad %31 %33
+         %93 = OpLoad %31 %35
+         %94 = OpFDiv %31 %92 %93
+               OpStore %91 %94
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  {
+    // Add synonym facts between the vector operands at pos and the operands to
+    // the scalar instruction.
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(100, {1}), MakeDataDescriptor(52, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(101, {1}), MakeDataDescriptor(53, {}));
+
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(103, {0}), MakeDataDescriptor(56, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(104, {0}), MakeDataDescriptor(57, {}));
+
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(106, {2}), MakeDataDescriptor(64, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(107, {2}), MakeDataDescriptor(65, {}));
+
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(109, {2}), MakeDataDescriptor(68, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(110, {2}), MakeDataDescriptor(69, {}));
+
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(112, {1}), MakeDataDescriptor(72, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(113, {1}), MakeDataDescriptor(73, {}));
+
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(115, {2}), MakeDataDescriptor(80, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(116, {2}), MakeDataDescriptor(81, {}));
+
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(118, {3}), MakeDataDescriptor(84, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(119, {3}), MakeDataDescriptor(85, {}));
+
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(121, {1}), MakeDataDescriptor(88, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(122, {1}), MakeDataDescriptor(89, {}));
+  }
+
+  // Test OpISub for signed integer.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_sub_int(54, 100, 101, 102, 1);
+    ASSERT_TRUE(
+        wrap_sub_int.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_sub_int, context.get(), &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(102, {1}), MakeDataDescriptor(54, {})));
+  }
+
+  // Test OpIMul for signed integer.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_mul_int(58, 103, 104, 105, 0);
+    ASSERT_TRUE(
+        wrap_mul_int.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_mul_int, context.get(), &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(105, {0}), MakeDataDescriptor(58, {})));
+  }
+
+  // Test OpIAdd for unsigned integer.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_add_uint(66, 106, 107, 108, 2);
+    ASSERT_TRUE(
+        wrap_add_uint.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_add_uint, context.get(),
+                          &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(108, {2}), MakeDataDescriptor(66, {})));
+  }
+
+  // Test OpISub for signed integer.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_sub_uint(70, 109, 110, 111, 2);
+    ASSERT_TRUE(
+        wrap_sub_uint.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_sub_uint, context.get(),
+                          &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(111, {2}), MakeDataDescriptor(70, {})));
+  }
+
+  // Test OpIMul for signed integer.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_mul_uint(74, 112, 113, 114, 1);
+    ASSERT_TRUE(
+        wrap_mul_uint.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_mul_uint, context.get(),
+                          &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(114, {1}), MakeDataDescriptor(74, {})));
+  }
+
+  // Test OpFAdd for float.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_add_float(82, 115, 116, 117, 2);
+    ASSERT_TRUE(
+        wrap_add_float.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_add_float, context.get(),
+                          &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(117, {2}), MakeDataDescriptor(82, {})));
+  }
+
+  // Test OpFSub for float.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_add_float(86, 118, 119, 120, 3);
+    ASSERT_TRUE(
+        wrap_add_float.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_add_float, context.get(),
+                          &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(120, {3}), MakeDataDescriptor(86, {})));
+  }
+
+  // Test OpFMul for float.
+  {
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_mul_float(90, 121, 122, 123, 1);
+    ASSERT_TRUE(
+        wrap_mul_float.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(wrap_mul_float, context.get(),
+                          &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(123, {1}), MakeDataDescriptor(90, {})));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %97
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 -5
+         %12 = OpTypeVector %6 2
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeInt 32 0
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 8
+         %23 = OpConstant %18 2
+         %24 = OpTypeVector %18 3
+         %25 = OpTypePointer Function %24
+         %31 = OpTypeFloat 32
+         %32 = OpTypePointer Function %31
+         %34 = OpConstant %31 3.29999995
+         %36 = OpConstant %31 1.10000002
+         %37 = OpTypeVector %31 4
+         %38 = OpTypePointer Function %37
+         %96 = OpTypePointer Input %31
+         %97 = OpVariable %96 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %33 = OpVariable %32 Function
+         %35 = OpVariable %32 Function
+         %39 = OpVariable %38 Function
+         %47 = OpVariable %7 Function
+         %51 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %59 = OpVariable %7 Function
+         %63 = OpVariable %19 Function
+         %67 = OpVariable %19 Function
+         %71 = OpVariable %19 Function
+         %75 = OpVariable %19 Function
+         %79 = OpVariable %32 Function
+         %83 = OpVariable %32 Function
+         %87 = OpVariable %32 Function
+         %91 = OpVariable %32 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+               OpStore %20 %21
+               OpStore %22 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %22
+         %30 = OpCompositeConstruct %24 %27 %28 %29
+               OpStore %26 %30
+               OpStore %33 %34
+               OpStore %35 %36
+         %40 = OpLoad %31 %33
+         %41 = OpLoad %31 %33
+         %42 = OpLoad %31 %35
+         %43 = OpLoad %31 %35
+         %44 = OpCompositeConstruct %37 %40 %41 %42 %43
+         %45 = OpLoad %37 %39
+         %46 = OpVectorShuffle %37 %45 %44 5 6 7 4
+               OpStore %39 %46
+         %48 = OpLoad %6 %8
+         %49 = OpLoad %6 %10
+         %50 = OpIAdd %6 %48 %49
+               OpStore %47 %50
+         %52 = OpLoad %6 %8
+         %53 = OpLoad %6 %10
+        %100 = OpCompositeConstruct %12 %52 %52
+        %101 = OpCompositeConstruct %12 %53 %53
+        %102 = OpISub %12 %100 %101
+         %54 = OpISub %6 %52 %53
+               OpStore %51 %54
+         %56 = OpLoad %6 %8
+         %57 = OpLoad %6 %10
+        %103 = OpCompositeConstruct %12 %56 %56
+        %104 = OpCompositeConstruct %12 %57 %57
+        %105 = OpIMul %12 %103 %104
+         %58 = OpIMul %6 %56 %57
+               OpStore %55 %58
+         %60 = OpLoad %6 %8
+         %61 = OpLoad %6 %10
+         %62 = OpSDiv %6 %60 %61
+               OpStore %59 %62
+         %64 = OpLoad %18 %20
+         %65 = OpLoad %18 %22
+        %106 = OpCompositeConstruct %24 %64 %64 %64
+        %107 = OpCompositeConstruct %24 %65 %65 %65
+        %108 = OpIAdd %24 %106 %107
+         %66 = OpIAdd %18 %64 %65
+               OpStore %63 %66
+         %68 = OpLoad %18 %20
+         %69 = OpLoad %18 %22
+        %109 = OpCompositeConstruct %24 %68 %68 %68
+        %110 = OpCompositeConstruct %24 %69 %69 %69
+        %111 = OpISub %24 %109 %110
+         %70 = OpISub %18 %68 %69
+               OpStore %67 %70
+         %72 = OpLoad %18 %20
+         %73 = OpLoad %18 %22
+        %112 = OpCompositeConstruct %24 %72 %72 %72
+        %113 = OpCompositeConstruct %24 %73 %73 %73
+        %114 = OpIMul %24 %112 %113
+         %74 = OpIMul %18 %72 %73
+               OpStore %71 %74
+         %76 = OpLoad %18 %20
+         %77 = OpLoad %18 %22
+         %78 = OpUDiv %18 %76 %77
+               OpStore %75 %78
+         %80 = OpLoad %31 %33
+         %81 = OpLoad %31 %35
+        %115 = OpCompositeConstruct %37 %80 %80 %80 %80
+        %116 = OpCompositeConstruct %37 %81 %81 %81 %81
+        %117 = OpFAdd %37 %115 %116
+         %82 = OpFAdd %31 %80 %81
+               OpStore %79 %82
+         %84 = OpLoad %31 %33
+         %85 = OpLoad %31 %35
+        %118 = OpCompositeConstruct %37 %84 %84 %84 %84
+        %119 = OpCompositeConstruct %37 %85 %85 %85 %85
+        %120 = OpFSub %37 %118 %119
+         %86 = OpFSub %31 %84 %85
+               OpStore %83 %86
+         %88 = OpLoad %31 %33
+         %89 = OpLoad %31 %35
+        %121 = OpCompositeConstruct %37 %88 %88 %88 %88
+        %122 = OpCompositeConstruct %37 %89 %89 %89 %89
+        %123 = OpFMul %37 %121 %122
+         %90 = OpFMul %31 %88 %89
+               OpStore %87 %90
+         %92 = OpLoad %31 %33
+         %93 = OpLoad %31 %35
+         %94 = OpFDiv %31 %92 %93
+               OpStore %91 %94
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationWrapVectorSynonym, DivSupportTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %97
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 -5
+         %12 = OpTypeVector %6 2
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeInt 32 0
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 8
+         %23 = OpConstant %18 2
+         %24 = OpTypeVector %18 3
+         %25 = OpTypePointer Function %24
+         %31 = OpTypeFloat 32
+         %32 = OpTypePointer Function %31
+         %34 = OpConstant %31 3.29999995
+         %36 = OpConstant %31 1.10000002
+         %37 = OpTypeVector %31 4
+         %38 = OpTypePointer Function %37
+         %96 = OpTypePointer Input %31
+         %97 = OpVariable %96 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %33 = OpVariable %32 Function
+         %35 = OpVariable %32 Function
+         %39 = OpVariable %38 Function
+         %47 = OpVariable %7 Function
+         %51 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %59 = OpVariable %7 Function
+         %63 = OpVariable %19 Function
+         %67 = OpVariable %19 Function
+         %71 = OpVariable %19 Function
+         %75 = OpVariable %19 Function
+         %79 = OpVariable %32 Function
+         %83 = OpVariable %32 Function
+         %87 = OpVariable %32 Function
+         %91 = OpVariable %32 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+               OpStore %20 %21
+               OpStore %22 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %22
+         %30 = OpCompositeConstruct %24 %27 %28 %29
+               OpStore %26 %30
+               OpStore %33 %34
+               OpStore %35 %36
+         %40 = OpLoad %31 %33
+         %41 = OpLoad %31 %33
+         %42 = OpLoad %31 %35
+         %43 = OpLoad %31 %35
+         %44 = OpCompositeConstruct %37 %40 %41 %42 %43
+         %45 = OpLoad %37 %39
+         %46 = OpVectorShuffle %37 %45 %44 5 6 7 4
+               OpStore %39 %46
+         %48 = OpLoad %6 %8
+         %49 = OpLoad %6 %10
+         %50 = OpIAdd %6 %48 %49
+               OpStore %47 %50
+         %52 = OpLoad %6 %8
+         %53 = OpLoad %6 %10
+         %54 = OpISub %6 %52 %53
+               OpStore %51 %54
+         %56 = OpLoad %6 %8
+         %57 = OpLoad %6 %10
+         %58 = OpIMul %6 %56 %57
+               OpStore %55 %58
+         %60 = OpLoad %6 %8
+         %61 = OpLoad %6 %10
+        %100 = OpCompositeConstruct %12 %60 %60
+        %101 = OpCompositeConstruct %12 %61 %61
+         %62 = OpSDiv %6 %60 %61
+               OpStore %59 %62
+         %64 = OpLoad %18 %20
+         %65 = OpLoad %18 %22
+         %66 = OpIAdd %18 %64 %65
+               OpStore %63 %66
+         %68 = OpLoad %18 %20
+         %69 = OpLoad %18 %22
+         %70 = OpISub %18 %68 %69
+               OpStore %67 %70
+         %72 = OpLoad %18 %20
+         %73 = OpLoad %18 %22
+         %74 = OpIMul %18 %72 %73
+               OpStore %71 %74
+         %76 = OpLoad %18 %20
+         %77 = OpLoad %18 %22
+        %102 = OpCompositeConstruct %24 %76 %76 %76
+        %103 = OpCompositeConstruct %24 %77 %77 %77
+         %78 = OpUDiv %18 %76 %77
+               OpStore %75 %78
+         %80 = OpLoad %31 %33
+         %81 = OpLoad %31 %35
+         %82 = OpFAdd %31 %80 %81
+               OpStore %79 %82
+         %84 = OpLoad %31 %33
+         %85 = OpLoad %31 %35
+         %86 = OpFSub %31 %84 %85
+               OpStore %83 %86
+         %88 = OpLoad %31 %33
+         %89 = OpLoad %31 %35
+         %90 = OpFMul %31 %88 %89
+               OpStore %87 %90
+         %92 = OpLoad %31 %33
+         %93 = OpLoad %31 %35
+        %104 = OpCompositeConstruct %37 %92 %92 %92 %92
+        %105 = OpCompositeConstruct %37 %93 %93 %93 %93
+         %94 = OpFDiv %31 %92 %93
+               OpStore %91 %94
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(100, {1}), MakeDataDescriptor(60, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(101, {1}), MakeDataDescriptor(61, {}));
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(102, {1}), MakeDataDescriptor(76, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(103, {1}), MakeDataDescriptor(77, {}));
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(104, {1}), MakeDataDescriptor(92, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(105, {1}), MakeDataDescriptor(93, {}));
+
+  // Div operations are not currently supported.
+  {
+    TransformationWrapVectorSynonym wrap_div_bad1(62, 100, 101, 106, 1);
+    ASSERT_FALSE(
+        wrap_div_bad1.IsApplicable(context.get(), transformation_context));
+
+    TransformationWrapVectorSynonym wrap_div_bad2(78, 102, 103, 106, 1);
+    ASSERT_FALSE(
+        wrap_div_bad2.IsApplicable(context.get(), transformation_context));
+
+    TransformationWrapVectorSynonym wrap_div_bad3(94, 104, 105, 106, 1);
+    ASSERT_FALSE(
+        wrap_div_bad3.IsApplicable(context.get(), transformation_context));
+  }
+}
+
+TEST(TransformationWrapVectorSynonym, AdditionalWidthSupportTest) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %97
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 64 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 -5
+         %12 = OpTypeVector %6 2
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeInt 64 0
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 8
+         %23 = OpConstant %18 2
+         %24 = OpTypeVector %18 3
+         %25 = OpTypePointer Function %24
+         %31 = OpTypeFloat 64
+         %32 = OpTypePointer Function %31
+         %34 = OpConstant %31 3.29999995
+         %36 = OpConstant %31 1.10000002
+         %37 = OpTypeVector %31 4
+         %38 = OpTypePointer Function %37
+         %96 = OpTypePointer Input %31
+         %97 = OpVariable %96 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %33 = OpVariable %32 Function
+         %35 = OpVariable %32 Function
+         %39 = OpVariable %38 Function
+         %47 = OpVariable %7 Function
+         %51 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %59 = OpVariable %7 Function
+         %63 = OpVariable %19 Function
+         %67 = OpVariable %19 Function
+         %71 = OpVariable %19 Function
+         %75 = OpVariable %19 Function
+         %79 = OpVariable %32 Function
+         %83 = OpVariable %32 Function
+         %87 = OpVariable %32 Function
+         %91 = OpVariable %32 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+               OpStore %20 %21
+               OpStore %22 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %22
+         %30 = OpCompositeConstruct %24 %27 %28 %29
+               OpStore %26 %30
+               OpStore %33 %34
+               OpStore %35 %36
+         %40 = OpLoad %31 %33
+         %41 = OpLoad %31 %33
+         %42 = OpLoad %31 %35
+         %43 = OpLoad %31 %35
+         %44 = OpCompositeConstruct %37 %40 %41 %42 %43
+         %45 = OpLoad %37 %39
+         %46 = OpVectorShuffle %37 %45 %44 5 6 7 4
+               OpStore %39 %46
+         %48 = OpLoad %6 %8
+         %49 = OpLoad %6 %10
+        %100 = OpCompositeConstruct %12 %48 %48
+        %101 = OpCompositeConstruct %12 %49 %49
+         %50 = OpIAdd %6 %48 %49
+               OpStore %47 %50
+         %52 = OpLoad %6 %8
+         %53 = OpLoad %6 %10
+         %54 = OpISub %6 %52 %53
+               OpStore %51 %54
+         %56 = OpLoad %6 %8
+         %57 = OpLoad %6 %10
+         %58 = OpIMul %6 %56 %57
+               OpStore %55 %58
+         %60 = OpLoad %6 %8
+         %61 = OpLoad %6 %10
+         %62 = OpSDiv %6 %60 %61
+               OpStore %59 %62
+         %64 = OpLoad %18 %20
+         %65 = OpLoad %18 %22
+         %66 = OpIAdd %18 %64 %65
+               OpStore %63 %66
+         %68 = OpLoad %18 %20
+         %69 = OpLoad %18 %22
+        %103 = OpCompositeConstruct %24 %68 %68 %68
+        %104 = OpCompositeConstruct %24 %69 %69 %69
+         %70 = OpISub %18 %68 %69
+               OpStore %67 %70
+         %72 = OpLoad %18 %20
+         %73 = OpLoad %18 %22
+         %74 = OpIMul %18 %72 %73
+               OpStore %71 %74
+         %76 = OpLoad %18 %20
+         %77 = OpLoad %18 %22
+         %78 = OpUDiv %18 %76 %77
+               OpStore %75 %78
+         %80 = OpLoad %31 %33
+         %81 = OpLoad %31 %35
+         %82 = OpFAdd %31 %80 %81
+               OpStore %79 %82
+         %84 = OpLoad %31 %33
+         %85 = OpLoad %31 %35
+         %86 = OpFSub %31 %84 %85
+               OpStore %83 %86
+         %88 = OpLoad %31 %33
+         %89 = OpLoad %31 %35
+        %106 = OpCompositeConstruct %37 %88 %88 %88 %88
+        %107 = OpCompositeConstruct %37 %89 %89 %89 %89
+         %90 = OpFMul %31 %88 %89
+               OpStore %87 %90
+         %92 = OpLoad %31 %33
+         %93 = OpLoad %31 %35
+         %94 = OpFDiv %31 %92 %93
+               OpStore %91 %94
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  // Check context validity.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  // Vec Type Id |   Vector Type  |  Element Type id |   Element Type  |
+  // ------------+----------------+------------------+-----------------+
+  //     12      |      vec2      |         6        |      int64      |
+  //     24      |      vec3      |        18        |     uint64      |
+  //     37      |      vec4      |        31        |    float64      |
+
+  // Test support for 64-bit signed int.
+  {
+    // Assert that the target scalar instruction result id is relevant.
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(50));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(100, {1}), MakeDataDescriptor(48, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(101, {1}), MakeDataDescriptor(49, {}));
+
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_add_int64(50, 100, 101, 102, 1);
+    ASSERT_TRUE(
+        wrap_add_int64.IsApplicable(context.get(), transformation_context));
+    // Insert an arithmetic instruction of the same type to add two vectors.
+    ApplyAndCheckFreshIds(wrap_add_int64, context.get(),
+                          &transformation_context);
+
+    // |instruction_id| and id at |scalar_position of the result vector should
+    // be synonyms.
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(102, {1}), MakeDataDescriptor(50, {})));
+  }
+
+  // Test support for 64-bit unsigned int.
+  {
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(70));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(103, {2}), MakeDataDescriptor(68, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(104, {2}), MakeDataDescriptor(69, {}));
+
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_sub_uint64(70, 103, 104, 105, 2);
+    ASSERT_TRUE(
+        wrap_sub_uint64.IsApplicable(context.get(), transformation_context));
+
+    ApplyAndCheckFreshIds(wrap_sub_uint64, context.get(),
+                          &transformation_context);
+
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(105, {2}), MakeDataDescriptor(70, {})));
+  }
+
+  // Test support for 64-bit float.
+  {
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(90));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(106, {3}), MakeDataDescriptor(88, {}));
+    transformation_context.GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(107, {3}), MakeDataDescriptor(89, {}));
+
+    // Good: The following transformation should be applicable.
+    TransformationWrapVectorSynonym wrap_mul_float64(90, 106, 107, 108, 3);
+    ASSERT_TRUE(
+        wrap_mul_float64.IsApplicable(context.get(), transformation_context));
+
+    ApplyAndCheckFreshIds(wrap_mul_float64, context.get(),
+                          &transformation_context);
+
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(108, {3}), MakeDataDescriptor(90, {})));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+               OpCapability Int64
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %97
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 64 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 10
+         %11 = OpConstant %6 -5
+         %12 = OpTypeVector %6 2
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeInt 64 0
+         %19 = OpTypePointer Function %18
+         %21 = OpConstant %18 8
+         %23 = OpConstant %18 2
+         %24 = OpTypeVector %18 3
+         %25 = OpTypePointer Function %24
+         %31 = OpTypeFloat 64
+         %32 = OpTypePointer Function %31
+         %34 = OpConstant %31 3.29999995
+         %36 = OpConstant %31 1.10000002
+         %37 = OpTypeVector %31 4
+         %38 = OpTypePointer Function %37
+         %96 = OpTypePointer Input %31
+         %97 = OpVariable %96 Input
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %22 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+         %33 = OpVariable %32 Function
+         %35 = OpVariable %32 Function
+         %39 = OpVariable %38 Function
+         %47 = OpVariable %7 Function
+         %51 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %59 = OpVariable %7 Function
+         %63 = OpVariable %19 Function
+         %67 = OpVariable %19 Function
+         %71 = OpVariable %19 Function
+         %75 = OpVariable %19 Function
+         %79 = OpVariable %32 Function
+         %83 = OpVariable %32 Function
+         %87 = OpVariable %32 Function
+         %91 = OpVariable %32 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+               OpStore %20 %21
+               OpStore %22 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %22
+         %30 = OpCompositeConstruct %24 %27 %28 %29
+               OpStore %26 %30
+               OpStore %33 %34
+               OpStore %35 %36
+         %40 = OpLoad %31 %33
+         %41 = OpLoad %31 %33
+         %42 = OpLoad %31 %35
+         %43 = OpLoad %31 %35
+         %44 = OpCompositeConstruct %37 %40 %41 %42 %43
+         %45 = OpLoad %37 %39
+         %46 = OpVectorShuffle %37 %45 %44 5 6 7 4
+               OpStore %39 %46
+         %48 = OpLoad %6 %8
+         %49 = OpLoad %6 %10
+        %100 = OpCompositeConstruct %12 %48 %48
+        %101 = OpCompositeConstruct %12 %49 %49
+        %102 = OpIAdd %12 %100 %101
+         %50 = OpIAdd %6 %48 %49
+               OpStore %47 %50
+         %52 = OpLoad %6 %8
+         %53 = OpLoad %6 %10
+         %54 = OpISub %6 %52 %53
+               OpStore %51 %54
+         %56 = OpLoad %6 %8
+         %57 = OpLoad %6 %10
+         %58 = OpIMul %6 %56 %57
+               OpStore %55 %58
+         %60 = OpLoad %6 %8
+         %61 = OpLoad %6 %10
+         %62 = OpSDiv %6 %60 %61
+               OpStore %59 %62
+         %64 = OpLoad %18 %20
+         %65 = OpLoad %18 %22
+         %66 = OpIAdd %18 %64 %65
+               OpStore %63 %66
+         %68 = OpLoad %18 %20
+         %69 = OpLoad %18 %22
+        %103 = OpCompositeConstruct %24 %68 %68 %68
+        %104 = OpCompositeConstruct %24 %69 %69 %69
+        %105 = OpISub %24 %103 %104
+         %70 = OpISub %18 %68 %69
+               OpStore %67 %70
+         %72 = OpLoad %18 %20
+         %73 = OpLoad %18 %22
+         %74 = OpIMul %18 %72 %73
+               OpStore %71 %74
+         %76 = OpLoad %18 %20
+         %77 = OpLoad %18 %22
+         %78 = OpUDiv %18 %76 %77
+               OpStore %75 %78
+         %80 = OpLoad %31 %33
+         %81 = OpLoad %31 %35
+         %82 = OpFAdd %31 %80 %81
+               OpStore %79 %82
+         %84 = OpLoad %31 %33
+         %85 = OpLoad %31 %35
+         %86 = OpFSub %31 %84 %85
+               OpStore %83 %86
+         %88 = OpLoad %31 %33
+         %89 = OpLoad %31 %35
+        %106 = OpCompositeConstruct %37 %88 %88 %88 %88
+        %107 = OpCompositeConstruct %37 %89 %89 %89 %89
+        %108 = OpFMul %37 %106 %107
+         %90 = OpFMul %31 %88 %89
+               OpStore %87 %90
+         %92 = OpLoad %31 %33
+         %93 = OpLoad %31 %35
+         %94 = OpFDiv %31 %92 %93
+               OpStore %91 %94
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationWrapVectorSynonym, DifferentVectorSignedness) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %12 = OpConstantComposite %7 %10 %11
+         %14 = OpTypeInt 32 0
+         %15 = OpTypeVector %14 2
+         %18 = OpConstant %14 3
+         %19 = OpConstant %14 0
+         %20 = OpConstantComposite %15 %18 %19
+         %21 = OpConstantComposite %15 %19 %18
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %100 = OpIAdd %14 %10 %18
+        %101 = OpIAdd %6 %10 %18
+        %102 = OpIAdd %6 %18 %19
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  // Check context validity.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(18, {}), MakeDataDescriptor(20, {0}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(19, {}), MakeDataDescriptor(21, {0}));
+
+  {
+    TransformationWrapVectorSynonym transformation1(100, 12, 20, 200, 0);
+    ASSERT_TRUE(
+        transformation1.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation1, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(200, {0}), MakeDataDescriptor(100, {})));
+  }
+
+  {
+    TransformationWrapVectorSynonym transformation2(101, 12, 20, 201, 0);
+    ASSERT_TRUE(
+        transformation2.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation2, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(201, {0}), MakeDataDescriptor(101, {})));
+  }
+
+  {
+    TransformationWrapVectorSynonym transformation3(102, 20, 21, 202, 0);
+    ASSERT_TRUE(
+        transformation3.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation3, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(202, {0}), MakeDataDescriptor(102, {})));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %12 = OpConstantComposite %7 %10 %11
+         %14 = OpTypeInt 32 0
+         %15 = OpTypeVector %14 2
+         %18 = OpConstant %14 3
+         %19 = OpConstant %14 0
+         %20 = OpConstantComposite %15 %18 %19
+         %21 = OpConstantComposite %15 %19 %18
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %200 = OpIAdd %15 %12 %20
+        %100 = OpIAdd %14 %10 %18
+        %201 = OpIAdd %7 %12 %20
+        %101 = OpIAdd %6 %10 %18
+        %202 = OpIAdd %7 %20 %21
+        %102 = OpIAdd %6 %18 %19
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationWrapVectorSynonym, SignednessDoesNotMatchResultType) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeVector %6 2
+          %8 = OpTypePointer Function %7
+         %10 = OpConstant %6 1
+         %11 = OpConstant %6 0
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpConstantComposite %7 %11 %10
+         %14 = OpTypeInt 32 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+        %100 = OpIAdd %14 %10 %11
+               OpReturn
+               OpFunctionEnd
+  )";
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+
+  // Check context validity.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(13, {0}));
+
+  ASSERT_FALSE(TransformationWrapVectorSynonym(100, 12, 13, 200, 0)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn
index ec09b2b..2aef4e8 100644
--- a/test/fuzzers/BUILD.gn
+++ b/test/fuzzers/BUILD.gn
@@ -48,6 +48,7 @@
   source_set(target_name) {
     testonly = true
     sources = invoker.sources
+    sources += [ "random_generator.cpp" ]
     deps = [
       "../..:spvtools",
       "../..:spvtools_opt",
diff --git a/test/fuzzers/CMakeLists.txt b/test/fuzzers/CMakeLists.txt
new file mode 100644
index 0000000..50c4589
--- /dev/null
+++ b/test/fuzzers/CMakeLists.txt
@@ -0,0 +1,56 @@
+# Copyright (c) 2021 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+function(add_spvtools_libfuzzer_target)
+  set(one_value_args TARGET)
+  set(multi_value_args SRCS LIBS)
+  cmake_parse_arguments(
+    ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN})
+
+  add_executable(${ARG_TARGET} ${ARG_SRCS})
+  spvtools_default_compile_options(${ARG_TARGET})
+  target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS})
+  target_include_directories(${ARG_TARGET} PRIVATE
+    ${spirv-tools_SOURCE_DIR}
+    ${spirv-tools_BINARY_DIR}
+  )
+  set_property(TARGET ${ARG_TARGET} PROPERTY FOLDER "SPIRV-Tools libFuzzer targets")
+  if(NOT ${SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS} STREQUAL "")
+    # This is set when the fuzzers are being built by OSS-Fuzz. In this case the
+    # variable provides the necessary linker flags, and OSS-Fuzz will take care
+    # of passing suitable compiler flags.
+    target_link_options(${ARG_TARGET} PRIVATE ${SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS})
+  else()
+    # When the fuzzers are being built outside of OSS-Fuzz, standard libFuzzer
+    # arguments to enable fuzzing are used.
+    target_compile_options(${ARG_TARGET} PRIVATE "-fsanitize=fuzzer")
+    target_link_options(${ARG_TARGET} PRIVATE "-fsanitize=fuzzer")    
+  endif()
+endfunction()
+
+if (${SPIRV_BUILD_LIBFUZZER_TARGETS})
+  if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+    message(FATAL_ERROR "The libFuzzer targets are only supported with the Clang compiler. Compiler '${CMAKE_CXX_COMPILER_ID}' is not supported!")
+  endif()
+  add_spvtools_libfuzzer_target(TARGET spvtools_as_fuzzer SRCS spvtools_as_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_binary_parser_fuzzer SRCS spvtools_binary_parser_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_dis_fuzzer SRCS spvtools_dis_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_opt_legalization_fuzzer SRCS spvtools_opt_legalization_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_opt_performance_fuzzer SRCS spvtools_opt_performance_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_opt_size_fuzzer SRCS spvtools_opt_size_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_libfuzzer_target(TARGET spvtools_val_fuzzer SRCS spvtools_val_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  if (${SPIRV_BUILD_FUZZER})
+    add_spvtools_libfuzzer_target(TARGET spvtools_fuzz_fuzzer SRCS spvtools_fuzz_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS_FULL_VISIBILITY})
+  endif()
+endif()
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_000.spv b/test/fuzzers/corpora/spv/graphicsfuzz_000.spv
new file mode 100644
index 0000000..c203cec
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_000.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_001.spv b/test/fuzzers/corpora/spv/graphicsfuzz_001.spv
new file mode 100644
index 0000000..c3dfbd9
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_001.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_002.spv b/test/fuzzers/corpora/spv/graphicsfuzz_002.spv
new file mode 100644
index 0000000..5a3c145
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_002.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_003.spv b/test/fuzzers/corpora/spv/graphicsfuzz_003.spv
new file mode 100644
index 0000000..c0e0a29
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_003.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_004.spv b/test/fuzzers/corpora/spv/graphicsfuzz_004.spv
new file mode 100644
index 0000000..f1afc72
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_004.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_005.spv b/test/fuzzers/corpora/spv/graphicsfuzz_005.spv
new file mode 100644
index 0000000..c6bf437
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_005.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_006.spv b/test/fuzzers/corpora/spv/graphicsfuzz_006.spv
new file mode 100644
index 0000000..d0d4a70
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_006.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_007.spv b/test/fuzzers/corpora/spv/graphicsfuzz_007.spv
new file mode 100644
index 0000000..7f2f982
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_007.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_008.spv b/test/fuzzers/corpora/spv/graphicsfuzz_008.spv
new file mode 100644
index 0000000..c67dc97
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_008.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_009.spv b/test/fuzzers/corpora/spv/graphicsfuzz_009.spv
new file mode 100644
index 0000000..68740b3
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_009.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_010.spv b/test/fuzzers/corpora/spv/graphicsfuzz_010.spv
new file mode 100644
index 0000000..fedc8b4
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_010.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_011.spv b/test/fuzzers/corpora/spv/graphicsfuzz_011.spv
new file mode 100644
index 0000000..882bfe6
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_011.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_012.spv b/test/fuzzers/corpora/spv/graphicsfuzz_012.spv
new file mode 100644
index 0000000..bd3c114
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_012.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_013.spv b/test/fuzzers/corpora/spv/graphicsfuzz_013.spv
new file mode 100644
index 0000000..d7e35b4
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_013.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_014.spv b/test/fuzzers/corpora/spv/graphicsfuzz_014.spv
new file mode 100644
index 0000000..abf5488
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_014.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_015.spv b/test/fuzzers/corpora/spv/graphicsfuzz_015.spv
new file mode 100644
index 0000000..868ab04
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_015.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_016.spv b/test/fuzzers/corpora/spv/graphicsfuzz_016.spv
new file mode 100644
index 0000000..a7cef28
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_016.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_017.spv b/test/fuzzers/corpora/spv/graphicsfuzz_017.spv
new file mode 100644
index 0000000..6d338a9
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_017.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_018.spv b/test/fuzzers/corpora/spv/graphicsfuzz_018.spv
new file mode 100644
index 0000000..c7bf440
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_018.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_019.spv b/test/fuzzers/corpora/spv/graphicsfuzz_019.spv
new file mode 100644
index 0000000..99d7e2d
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_019.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_020.spv b/test/fuzzers/corpora/spv/graphicsfuzz_020.spv
new file mode 100644
index 0000000..9e24124
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_020.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_021.spv b/test/fuzzers/corpora/spv/graphicsfuzz_021.spv
new file mode 100644
index 0000000..02734af
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_021.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_022.spv b/test/fuzzers/corpora/spv/graphicsfuzz_022.spv
new file mode 100644
index 0000000..cd9ab76
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_022.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_023.spv b/test/fuzzers/corpora/spv/graphicsfuzz_023.spv
new file mode 100644
index 0000000..d0ec5d3
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_023.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_024.spv b/test/fuzzers/corpora/spv/graphicsfuzz_024.spv
new file mode 100644
index 0000000..503627f
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_024.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_025.spv b/test/fuzzers/corpora/spv/graphicsfuzz_025.spv
new file mode 100644
index 0000000..c4e0aa3
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_025.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_026.spv b/test/fuzzers/corpora/spv/graphicsfuzz_026.spv
new file mode 100644
index 0000000..c5b3ced
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_026.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_027.spv b/test/fuzzers/corpora/spv/graphicsfuzz_027.spv
new file mode 100644
index 0000000..626e606
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_027.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_028.spv b/test/fuzzers/corpora/spv/graphicsfuzz_028.spv
new file mode 100644
index 0000000..a9a4357
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_028.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_029.spv b/test/fuzzers/corpora/spv/graphicsfuzz_029.spv
new file mode 100644
index 0000000..18e49a5
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_029.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_030.spv b/test/fuzzers/corpora/spv/graphicsfuzz_030.spv
new file mode 100644
index 0000000..ac4ea36
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_030.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_031.spv b/test/fuzzers/corpora/spv/graphicsfuzz_031.spv
new file mode 100644
index 0000000..2f8eff6
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_031.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_032.spv b/test/fuzzers/corpora/spv/graphicsfuzz_032.spv
new file mode 100644
index 0000000..a5808ec
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_032.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_033.spv b/test/fuzzers/corpora/spv/graphicsfuzz_033.spv
new file mode 100644
index 0000000..98cf02e
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_033.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_034.spv b/test/fuzzers/corpora/spv/graphicsfuzz_034.spv
new file mode 100644
index 0000000..306fd84
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_034.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_035.spv b/test/fuzzers/corpora/spv/graphicsfuzz_035.spv
new file mode 100644
index 0000000..7ed19e0
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_035.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_036.spv b/test/fuzzers/corpora/spv/graphicsfuzz_036.spv
new file mode 100644
index 0000000..22e6783
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_036.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_037.spv b/test/fuzzers/corpora/spv/graphicsfuzz_037.spv
new file mode 100644
index 0000000..19fbd20
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_037.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_038.spv b/test/fuzzers/corpora/spv/graphicsfuzz_038.spv
new file mode 100644
index 0000000..7bc5489
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_038.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_039.spv b/test/fuzzers/corpora/spv/graphicsfuzz_039.spv
new file mode 100644
index 0000000..01b908d
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_039.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_040.spv b/test/fuzzers/corpora/spv/graphicsfuzz_040.spv
new file mode 100644
index 0000000..bd13157
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_040.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_041.spv b/test/fuzzers/corpora/spv/graphicsfuzz_041.spv
new file mode 100644
index 0000000..6e355c2
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_041.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_042.spv b/test/fuzzers/corpora/spv/graphicsfuzz_042.spv
new file mode 100644
index 0000000..d356cb1
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_042.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_043.spv b/test/fuzzers/corpora/spv/graphicsfuzz_043.spv
new file mode 100644
index 0000000..49c87ed
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_043.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_044.spv b/test/fuzzers/corpora/spv/graphicsfuzz_044.spv
new file mode 100644
index 0000000..c7e36d5
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_044.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_045.spv b/test/fuzzers/corpora/spv/graphicsfuzz_045.spv
new file mode 100644
index 0000000..ad19726
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_045.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_046.spv b/test/fuzzers/corpora/spv/graphicsfuzz_046.spv
new file mode 100644
index 0000000..3b3678a
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_046.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_047.spv b/test/fuzzers/corpora/spv/graphicsfuzz_047.spv
new file mode 100644
index 0000000..7253247
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_047.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_048.spv b/test/fuzzers/corpora/spv/graphicsfuzz_048.spv
new file mode 100644
index 0000000..5c05c1c
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_048.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_049.spv b/test/fuzzers/corpora/spv/graphicsfuzz_049.spv
new file mode 100644
index 0000000..22bc9c2
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_049.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_050.spv b/test/fuzzers/corpora/spv/graphicsfuzz_050.spv
new file mode 100644
index 0000000..8f84788
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_050.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_051.spv b/test/fuzzers/corpora/spv/graphicsfuzz_051.spv
new file mode 100644
index 0000000..727c64d
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_051.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_052.spv b/test/fuzzers/corpora/spv/graphicsfuzz_052.spv
new file mode 100644
index 0000000..d9ba741
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_052.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_053.spv b/test/fuzzers/corpora/spv/graphicsfuzz_053.spv
new file mode 100644
index 0000000..d827004
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_053.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_054.spv b/test/fuzzers/corpora/spv/graphicsfuzz_054.spv
new file mode 100644
index 0000000..a3aec7b
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_054.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_055.spv b/test/fuzzers/corpora/spv/graphicsfuzz_055.spv
new file mode 100644
index 0000000..2da1375
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_055.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_056.spv b/test/fuzzers/corpora/spv/graphicsfuzz_056.spv
new file mode 100644
index 0000000..515d4f1
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_056.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_057.spv b/test/fuzzers/corpora/spv/graphicsfuzz_057.spv
new file mode 100644
index 0000000..2a0f489
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_057.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_058.spv b/test/fuzzers/corpora/spv/graphicsfuzz_058.spv
new file mode 100644
index 0000000..3211ec2
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_058.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_059.spv b/test/fuzzers/corpora/spv/graphicsfuzz_059.spv
new file mode 100644
index 0000000..89e34ec
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_059.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_060.spv b/test/fuzzers/corpora/spv/graphicsfuzz_060.spv
new file mode 100644
index 0000000..d1fdaec
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_060.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_061.spv b/test/fuzzers/corpora/spv/graphicsfuzz_061.spv
new file mode 100644
index 0000000..de10f68
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_061.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_062.spv b/test/fuzzers/corpora/spv/graphicsfuzz_062.spv
new file mode 100644
index 0000000..503304c
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_062.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_063.spv b/test/fuzzers/corpora/spv/graphicsfuzz_063.spv
new file mode 100644
index 0000000..75d9b4c
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_063.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_064.spv b/test/fuzzers/corpora/spv/graphicsfuzz_064.spv
new file mode 100644
index 0000000..0b902cb
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_064.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_065.spv b/test/fuzzers/corpora/spv/graphicsfuzz_065.spv
new file mode 100644
index 0000000..437e45b
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_065.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_066.spv b/test/fuzzers/corpora/spv/graphicsfuzz_066.spv
new file mode 100644
index 0000000..5585a6f
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_066.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_067.spv b/test/fuzzers/corpora/spv/graphicsfuzz_067.spv
new file mode 100644
index 0000000..763fd34
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_067.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_068.spv b/test/fuzzers/corpora/spv/graphicsfuzz_068.spv
new file mode 100644
index 0000000..1ad0bd5
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_068.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_069.spv b/test/fuzzers/corpora/spv/graphicsfuzz_069.spv
new file mode 100644
index 0000000..f1e7950
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_069.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_070.spv b/test/fuzzers/corpora/spv/graphicsfuzz_070.spv
new file mode 100644
index 0000000..47f4c82
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_070.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_071.spv b/test/fuzzers/corpora/spv/graphicsfuzz_071.spv
new file mode 100644
index 0000000..3eac74a
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_071.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_072.spv b/test/fuzzers/corpora/spv/graphicsfuzz_072.spv
new file mode 100644
index 0000000..e246115
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_072.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_073.spv b/test/fuzzers/corpora/spv/graphicsfuzz_073.spv
new file mode 100644
index 0000000..b7e915b
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_073.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_074.spv b/test/fuzzers/corpora/spv/graphicsfuzz_074.spv
new file mode 100644
index 0000000..3588757
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_074.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_075.spv b/test/fuzzers/corpora/spv/graphicsfuzz_075.spv
new file mode 100644
index 0000000..d3dc46d
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_075.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_076.spv b/test/fuzzers/corpora/spv/graphicsfuzz_076.spv
new file mode 100644
index 0000000..11bee68
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_076.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_077.spv b/test/fuzzers/corpora/spv/graphicsfuzz_077.spv
new file mode 100644
index 0000000..d1b8c76
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_077.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_078.spv b/test/fuzzers/corpora/spv/graphicsfuzz_078.spv
new file mode 100644
index 0000000..4739a9a
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_078.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_079.spv b/test/fuzzers/corpora/spv/graphicsfuzz_079.spv
new file mode 100644
index 0000000..6553ecf
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_079.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_080.spv b/test/fuzzers/corpora/spv/graphicsfuzz_080.spv
new file mode 100644
index 0000000..802375b
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_080.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_081.spv b/test/fuzzers/corpora/spv/graphicsfuzz_081.spv
new file mode 100644
index 0000000..72d1027
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_081.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_082.spv b/test/fuzzers/corpora/spv/graphicsfuzz_082.spv
new file mode 100644
index 0000000..619c742
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_082.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_083.spv b/test/fuzzers/corpora/spv/graphicsfuzz_083.spv
new file mode 100644
index 0000000..fa27c33
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_083.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_084.spv b/test/fuzzers/corpora/spv/graphicsfuzz_084.spv
new file mode 100644
index 0000000..14f7bfd
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_084.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_085.spv b/test/fuzzers/corpora/spv/graphicsfuzz_085.spv
new file mode 100644
index 0000000..5f5fc03
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_085.spv
Binary files differ
diff --git a/test/fuzzers/corpora/spv/graphicsfuzz_086.spv b/test/fuzzers/corpora/spv/graphicsfuzz_086.spv
new file mode 100644
index 0000000..a311992
--- /dev/null
+++ b/test/fuzzers/corpora/spv/graphicsfuzz_086.spv
Binary files differ
diff --git a/test/fuzzers/random_generator.cpp b/test/fuzzers/random_generator.cpp
new file mode 100644
index 0000000..801a9ff
--- /dev/null
+++ b/test/fuzzers/random_generator.cpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2021 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 "test/fuzzers/random_generator.h"
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+
+namespace spvtools {
+namespace fuzzers {
+
+namespace {
+/// Generate integer from uniform distribution
+/// @tparam I - integer type
+/// @param engine - random number engine to use
+/// @param lower - Lower bound of integer generated
+/// @param upper - Upper bound of integer generated
+/// @returns i, where lower <= i < upper
+template <typename I>
+I RandomUInt(std::mt19937_64* engine, I lower, I upper) {
+  assert(lower < upper && "|lower| must be stictly less than |upper|");
+  return std::uniform_int_distribution<I>(lower, upper - 1)(*engine);
+}
+
+/// Helper for obtaining a seed bias value for HashCombine with a bit-width
+/// dependent on the size of size_t.
+template <int SIZE_OF_SIZE_T>
+struct HashCombineOffset {};
+/// Specialization of HashCombineOffset for size_t == 4.
+template <>
+struct HashCombineOffset<4> {
+  /// @returns the seed bias value for HashCombine()
+  static constexpr inline uint32_t value() {
+    return 0x9e3779b9;  // Fractional portion of Golden Ratio, suggested by
+                        // Linux Kernel and Knuth's Art of Computer Programming
+  }
+};
+/// Specialization of HashCombineOffset for size_t == 8.
+template <>
+struct HashCombineOffset<8> {
+  /// @returns the seed bias value for HashCombine()
+  static constexpr inline uint64_t value() {
+    return 0x9e3779b97f4a7c16;  // Fractional portion of Golden Ratio, suggested
+                                // by Linux Kernel and Knuth's Art of Computer
+                                // Programming
+  }
+};
+
+/// HashCombine "hashes" together an existing hash and hashable values.
+template <typename T>
+void HashCombine(size_t* hash, const T& value) {
+  constexpr size_t offset = HashCombineOffset<sizeof(size_t)>::value();
+  *hash ^= std::hash<T>()(value) + offset + (*hash << 6) + (*hash >> 2);
+}
+
+/// Calculate the hash for the contents of a C-style data buffer
+/// @param data - pointer to buffer to be hashed
+/// @param size - number of elements in buffer
+/// @returns hash of the data in the buffer
+size_t HashBuffer(const uint8_t* data, const size_t size) {
+  size_t hash =
+      static_cast<size_t>(0xCA8945571519E991);  // seed with an arbitrary prime
+  HashCombine(&hash, size);
+  for (size_t i = 0; i < size; i++) {
+    HashCombine(&hash, data[i]);
+  }
+  return hash;
+}
+
+}  // namespace
+
+RandomGenerator::RandomGenerator(uint64_t seed) : engine_(seed) {}
+
+RandomGenerator::RandomGenerator(const uint8_t* data, size_t size) {
+  RandomGenerator(RandomGenerator::CalculateSeed(data, size));
+}
+
+spv_target_env RandomGenerator::GetTargetEnv() {
+  spv_target_env result;
+
+  // Need to check that the generated value isn't for a deprecated target env.
+  do {
+    result = static_cast<spv_target_env>(
+        RandomUInt(&engine_, 0u, static_cast<unsigned int>(SPV_ENV_MAX)));
+  } while (!spvIsValidEnv(result));
+
+  return result;
+}
+
+uint32_t RandomGenerator::GetUInt32(uint32_t lower, uint32_t upper) {
+  return RandomUInt(&engine_, lower, upper);
+}
+
+uint32_t RandomGenerator::GetUInt32(uint32_t bound) {
+  assert(bound > 0 && "|bound| must be greater than 0");
+  return RandomUInt(&engine_, 0u, bound);
+}
+
+uint64_t RandomGenerator::CalculateSeed(const uint8_t* data, size_t size) {
+  assert(data != nullptr && "|data| must be !nullptr");
+
+  // Number of bytes we want to skip at the start of data for the hash.
+  // Fewer bytes may be skipped when `size` is small.
+  // Has lower precedence than kHashDesiredMinBytes.
+  static const int64_t kHashDesiredLeadingSkipBytes = 5;
+  // Minimum number of bytes we want to use in the hash.
+  // Used for short buffers.
+  static const int64_t kHashDesiredMinBytes = 4;
+  // Maximum number of bytes we want to use in the hash.
+  static const int64_t kHashDesiredMaxBytes = 32;
+  int64_t size_i64 = static_cast<int64_t>(size);
+  int64_t hash_begin_i64 =
+      std::min(kHashDesiredLeadingSkipBytes,
+               std::max<int64_t>(size_i64 - kHashDesiredMinBytes, 0));
+  int64_t hash_end_i64 =
+      std::min(hash_begin_i64 + kHashDesiredMaxBytes, size_i64);
+  size_t hash_begin = static_cast<size_t>(hash_begin_i64);
+  size_t hash_size = static_cast<size_t>(hash_end_i64) - hash_begin;
+  return HashBuffer(data + hash_begin, hash_size);
+}
+
+}  // namespace fuzzers
+}  // namespace spvtools
diff --git a/test/fuzzers/random_generator.h b/test/fuzzers/random_generator.h
new file mode 100644
index 0000000..b121fe8
--- /dev/null
+++ b/test/fuzzers/random_generator.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2021 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 TEST_FUZZERS_RANDOM_GENERATOR_H_
+#define TEST_FUZZERS_RANDOM_GENERATOR_H_
+
+#include <cstdint>
+#include <random>
+
+#include "source/spirv_target_env.h"
+
+namespace spvtools {
+namespace fuzzers {
+
+/// Pseudo random generator utility class for fuzzing
+class RandomGenerator {
+ public:
+  /// @brief Initializes the internal engine
+  /// @param seed - seed value passed to engine
+  explicit RandomGenerator(uint64_t seed);
+
+  /// @brief Initializes the internal engine
+  /// @param data - data to calculate the seed from
+  /// @param size - size of the data
+  explicit RandomGenerator(const uint8_t* data, size_t size);
+
+  ~RandomGenerator() {}
+
+  /// Calculate a seed value based on a blob of data.
+  /// Currently hashes bytes near the front of the buffer, after skipping N
+  /// bytes.
+  /// @param data - pointer to data to base calculation off of, must be !nullptr
+  /// @param size - number of elements in |data|, must be > 0
+  static uint64_t CalculateSeed(const uint8_t* data, size_t size);
+
+  /// Get random valid target env.
+  spv_target_env GetTargetEnv();
+
+  /// Get uint32_t value from uniform distribution.
+  /// @param lower - lower bound of integer generated
+  /// @param upper - upper bound of integer generated
+  /// @returns i, where lower <= i < upper
+  uint32_t GetUInt32(uint32_t lower, uint32_t upper);
+
+  /// Get uint32_t value from uniform distribution.
+  /// @param bound - Upper bound of integer generated
+  /// @returns i, where 0 <= i < bound
+  uint32_t GetUInt32(uint32_t bound);
+
+ private:
+  std::mt19937_64 engine_;
+};  // class RandomGenerator
+
+}  // namespace fuzzers
+}  // namespace spvtools
+
+#endif  // TEST_FUZZERS_RANDOM_GENERATOR_UTILS_H_
diff --git a/test/fuzzers/spvtools_as_fuzzer.cpp b/test/fuzzers/spvtools_as_fuzzer.cpp
index 8cecb05..ba3f5b9 100644
--- a/test/fuzzers/spvtools_as_fuzzer.cpp
+++ b/test/fuzzers/spvtools_as_fuzzer.cpp
@@ -18,18 +18,27 @@
 
 #include "source/spirv_target_env.h"
 #include "spirv-tools/libspirv.hpp"
+#include "test/fuzzers/random_generator.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < sizeof(spv_target_env) + 1) return 0;
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
+  if (size > 0) {
+    spvtools::fuzzers::RandomGenerator random_gen(data, size);
+    target_env = random_gen.GetTargetEnv();
+  }
 
-  const spv_context context =
-      spvContextCreate(*reinterpret_cast<const spv_target_env*>(data));
-  if (context == nullptr) return 0;
-
-  data += sizeof(spv_target_env);
-  size -= sizeof(spv_target_env);
+  const spv_context context = spvContextCreate(target_env);
+  if (context == nullptr) {
+    return 0;
+  }
 
   std::vector<uint32_t> input;
+  input.resize(size >> 2);
+  size_t count = 0;
+  for (size_t i = 0; (i + 3) < size; i += 4) {
+    input[count++] = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) |
+                     (data[i + 3]) << 24;
+  }
 
   std::vector<char> input_str;
   size_t char_count = input.size() * sizeof(uint32_t) / sizeof(char);
diff --git a/test/fuzzers/spvtools_binary_parser_fuzzer.cpp b/test/fuzzers/spvtools_binary_parser_fuzzer.cpp
index 76ba4d9..3a97db4 100644
--- a/test/fuzzers/spvtools_binary_parser_fuzzer.cpp
+++ b/test/fuzzers/spvtools_binary_parser_fuzzer.cpp
@@ -16,16 +16,18 @@
 #include <vector>
 
 #include "spirv-tools/libspirv.hpp"
+#include "test/fuzzers/random_generator.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < sizeof(spv_target_env) + 1) return 0;
+  if (size < 1) {
+    return 0;
+  }
 
-  const spv_context context =
-      spvContextCreate(*reinterpret_cast<const spv_target_env*>(data));
-  if (context == nullptr) return 0;
-
-  data += sizeof(spv_target_env);
-  size -= sizeof(spv_target_env);
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  const spv_context context = spvContextCreate(random_gen.GetTargetEnv());
+  if (context == nullptr) {
+    return 0;
+  }
 
   std::vector<uint32_t> input;
   input.resize(size >> 2);
diff --git a/test/fuzzers/spvtools_dis_fuzzer.cpp b/test/fuzzers/spvtools_dis_fuzzer.cpp
index ca9a52d..0cb0eff 100644
--- a/test/fuzzers/spvtools_dis_fuzzer.cpp
+++ b/test/fuzzers/spvtools_dis_fuzzer.cpp
@@ -18,16 +18,20 @@
 
 #include "source/spirv_target_env.h"
 #include "spirv-tools/libspirv.hpp"
+#include "test/fuzzers/random_generator.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < sizeof(spv_target_env) + 1) return 0;
+  if (size < 4) {
+    // There are not enough bytes to constitute a binary that can be
+    // disassembled.
+    return 0;
+  }
 
-  const spv_context context =
-      spvContextCreate(*reinterpret_cast<const spv_target_env*>(data));
-  if (context == nullptr) return 0;
-
-  data += sizeof(spv_target_env);
-  size -= sizeof(spv_target_env);
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  const spv_context context = spvContextCreate(random_gen.GetTargetEnv());
+  if (context == nullptr) {
+    return 0;
+  }
 
   std::vector<uint32_t> input;
   input.resize(size >> 2);
diff --git a/test/fuzzers/spvtools_fuzz_fuzzer.cpp b/test/fuzzers/spvtools_fuzz_fuzzer.cpp
new file mode 100644
index 0000000..d43920c
--- /dev/null
+++ b/test/fuzzers/spvtools_fuzz_fuzzer.cpp
@@ -0,0 +1,80 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cstdint>
+#include <vector>
+
+#include "source/fuzz/fuzzer.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "spirv-tools/libspirv.hpp"
+#include "test/fuzzers/random_generator.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  if (size == 0 || (size % sizeof(uint32_t)) != 0) {
+    // An empty binary, or a binary whose size is not a multiple of word-size,
+    // cannot be valid, so can be rejected immediately.
+    return 0;
+  }
+
+  std::vector<uint32_t> initial_binary(size / sizeof(uint32_t));
+  memcpy(initial_binary.data(), data, size);
+
+  spvtools::ValidatorOptions validator_options;
+
+  spvtools::MessageConsumer message_consumer =
+      [](spv_message_level_t, const char*, const spv_position_t&, const char*) {
+      };
+
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  auto target_env = random_gen.GetTargetEnv();
+  std::unique_ptr<spvtools::opt::IRContext> ir_context;
+  if (!spvtools::fuzz::fuzzerutil::BuildIRContext(
+          target_env, message_consumer, initial_binary, validator_options,
+          &ir_context)) {
+    // The input is invalid - give up.
+    return 0;
+  }
+
+  std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donor_suppliers = {
+      [&initial_binary, message_consumer, target_env,
+       &validator_options]() -> std::unique_ptr<spvtools::opt::IRContext> {
+        std::unique_ptr<spvtools::opt::IRContext> result;
+        if (!spvtools::fuzz::fuzzerutil::BuildIRContext(
+                target_env, message_consumer, initial_binary, validator_options,
+                &result)) {
+          // The input was successfully parsed and validated first time around,
+          // so something is wrong if it is now invalid.
+          abort();
+        }
+        return result;
+      }};
+
+  uint32_t seed = random_gen.GetUInt32(std::numeric_limits<uint32_t>::max());
+  auto fuzzer_context = spvtools::MakeUnique<spvtools::fuzz::FuzzerContext>(
+      spvtools::MakeUnique<spvtools::fuzz::PseudoRandomGenerator>(seed),
+      spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), false);
+
+  auto transformation_context =
+      spvtools::MakeUnique<spvtools::fuzz::TransformationContext>(
+          spvtools::MakeUnique<spvtools::fuzz::FactManager>(ir_context.get()),
+          validator_options);
+
+  spvtools::fuzz::Fuzzer fuzzer(
+      std::move(ir_context), std::move(transformation_context),
+      std::move(fuzzer_context), message_consumer, donor_suppliers, false,
+      spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations, true,
+      validator_options);
+  fuzzer.Run(0);
+  return 0;
+}
diff --git a/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp b/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp
index b45a98c..6f4d7e8 100644
--- a/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp
+++ b/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp
@@ -16,9 +16,15 @@
 #include <vector>
 
 #include "spirv-tools/optimizer.hpp"
+#include "test/fuzzers/random_generator.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_3);
+  if (size < 1) {
+    return 0;
+  }
+
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  spvtools::Optimizer optimizer(random_gen.GetTargetEnv());
   optimizer.SetMessageConsumer([](spv_message_level_t, const char*,
                                   const spv_position_t&, const char*) {});
 
diff --git a/test/fuzzers/spvtools_opt_performance_fuzzer.cpp b/test/fuzzers/spvtools_opt_performance_fuzzer.cpp
index 6c3bd6a..9c47d7d 100644
--- a/test/fuzzers/spvtools_opt_performance_fuzzer.cpp
+++ b/test/fuzzers/spvtools_opt_performance_fuzzer.cpp
@@ -16,9 +16,15 @@
 #include <vector>
 
 #include "spirv-tools/optimizer.hpp"
+#include "test/fuzzers/random_generator.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_3);
+  if (size < 1) {
+    return 0;
+  }
+
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  spvtools::Optimizer optimizer(random_gen.GetTargetEnv());
   optimizer.SetMessageConsumer([](spv_message_level_t, const char*,
                                   const spv_position_t&, const char*) {});
 
diff --git a/test/fuzzers/spvtools_opt_size_fuzzer.cpp b/test/fuzzers/spvtools_opt_size_fuzzer.cpp
index 68c7974..10fac42 100644
--- a/test/fuzzers/spvtools_opt_size_fuzzer.cpp
+++ b/test/fuzzers/spvtools_opt_size_fuzzer.cpp
@@ -16,9 +16,15 @@
 #include <vector>
 
 #include "spirv-tools/optimizer.hpp"
+#include "test/fuzzers/random_generator.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_3);
+  if (size < 1) {
+    return 0;
+  }
+
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  spvtools::Optimizer optimizer(random_gen.GetTargetEnv());
   optimizer.SetMessageConsumer([](spv_message_level_t, const char*,
                                   const spv_position_t&, const char*) {});
 
diff --git a/test/fuzzers/spvtools_val_fuzzer.cpp b/test/fuzzers/spvtools_val_fuzzer.cpp
index 5dc4303..fd6396c 100644
--- a/test/fuzzers/spvtools_val_fuzzer.cpp
+++ b/test/fuzzers/spvtools_val_fuzzer.cpp
@@ -16,9 +16,15 @@
 #include <vector>
 
 #include "spirv-tools/libspirv.hpp"
+#include "test/fuzzers/random_generator.h"
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_3);
+  if (size < 1) {
+    return 0;
+  }
+
+  spvtools::fuzzers::RandomGenerator random_gen(data, size);
+  spvtools::SpirvTools tools(random_gen.GetTargetEnv());
   tools.SetMessageConsumer([](spv_message_level_t, const char*,
                               const spv_position_t&, const char*) {});
 
diff --git a/test/hex_float_test.cpp b/test/hex_float_test.cpp
index c422f75..ffdb8bd 100644
--- a/test/hex_float_test.cpp
+++ b/test/hex_float_test.cpp
@@ -1325,6 +1325,76 @@
               Eq(std::numeric_limits<double>::lowest()));
 }
 
+template <typename T>
+struct StreamParseCase {
+  StreamParseCase(const std::string& lit, bool succ, const std::string& suffix,
+                  T value)
+      : literal(lit),
+        expect_success(succ),
+        expected_suffix(suffix),
+        expected_value(HexFloat<FloatProxy<T>>(value)) {}
+
+  std::string literal;
+  bool expect_success;
+  std::string expected_suffix;
+  HexFloat<FloatProxy<T>> expected_value;
+};
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const StreamParseCase<T>& fspc) {
+  os << "StreamParseCase(" << fspc.literal
+     << ", expect_succes:" << int(fspc.expect_success) << ","
+     << fspc.expected_suffix << "," << fspc.expected_value << ")";
+  return os;
+}
+
+using FloatStreamParseTest = ::testing::TestWithParam<StreamParseCase<float>>;
+
+TEST_P(FloatStreamParseTest, Samples) {
+  std::stringstream input(GetParam().literal);
+  HexFloat<FloatProxy<float>> parsed_value(0.0f);
+  // Hex floats must be read with the stream input operator.
+  input >> parsed_value;
+  if (GetParam().expect_success) {
+    EXPECT_FALSE(input.fail());
+    std::string suffix;
+    input >> suffix;
+    // EXPECT_EQ(suffix, GetParam().expected_suffix);
+    EXPECT_EQ(parsed_value.value().getAsFloat(),
+              GetParam().expected_value.value().getAsFloat());
+  } else {
+    EXPECT_TRUE(input.fail());
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    HexFloatExponentMissingDigits, FloatStreamParseTest,
+    ::testing::ValuesIn(std::vector<StreamParseCase<float>>{
+        {"0x1.0p1", true, "", 2.0f},
+        {"0x1.0p1a", true, "a", 2.0f},
+        {"-0x1.0p1f", true, "f", -2.0f},
+        {"0x1.0p", false, "", 0.0f},
+        {"0x1.0pa", false, "", 0.0f},
+        {"0x1.0p!", false, "", 0.0f},
+        {"0x1.0p+", false, "", 0.0f},
+        {"0x1.0p+a", false, "", 0.0f},
+        {"0x1.0p+!", false, "", 0.0f},
+        {"0x1.0p-", false, "", 0.0f},
+        {"0x1.0p-a", false, "", 0.0f},
+        {"0x1.0p-!", false, "", 0.0f},
+        {"0x1.0p++", false, "", 0.0f},
+        {"0x1.0p+-", false, "", 0.0f},
+        {"0x1.0p-+", false, "", 0.0f},
+        {"0x1.0p--", false, "", 0.0f}}));
+
+INSTANTIATE_TEST_SUITE_P(
+    HexFloatExponentTrailingSign, FloatStreamParseTest,
+    ::testing::ValuesIn(std::vector<StreamParseCase<float>>{
+        // Don't consume a sign after the binary exponent digits.
+        {"0x1.0p1", true, "", 2.0f},
+        {"0x1.0p1+", true, "+", 2.0f},
+        {"0x1.0p1-", true, "-", 2.0f}}));
+
 // TODO(awoloszyn): Add fp16 tests and HexFloatTraits.
 }  // namespace
 }  // namespace utils
diff --git a/kokoro/windows-msvc-2013-release/continuous.cfg b/test/lint/CMakeLists.txt
similarity index 71%
copy from kokoro/windows-msvc-2013-release/continuous.cfg
copy to test/lint/CMakeLists.txt
index 5dfcba6..09bc6d3 100644
--- a/kokoro/windows-msvc-2013-release/continuous.cfg
+++ b/test/lint/CMakeLists.txt
@@ -1,10 +1,10 @@
-# Copyright (c) 2018 Google LLC.
+# Copyright (c) 2021 Google LLC.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 #
-#      http://www.apache.org/licenses/LICENSE-2.0
+#     http://www.apache.org/licenses/LICENSE-2.0
 #
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
@@ -12,5 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Continuous build configuration.
-build_file: "SPIRV-Tools/kokoro/windows-msvc-2013-release/build.bat"
+add_spvtools_unittest(TARGET lint
+  SRCS divergence_analysis_test.cpp
+  LIBS SPIRV-Tools-lint SPIRV-Tools-opt
+)
diff --git a/test/lint/divergence_analysis_test.cpp b/test/lint/divergence_analysis_test.cpp
new file mode 100644
index 0000000..36cd32d
--- /dev/null
+++ b/test/lint/divergence_analysis_test.cpp
@@ -0,0 +1,700 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/lint/divergence_analysis.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "spirv-tools/libspirv.h"
+
+namespace spvtools {
+namespace lint {
+namespace {
+
+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;
+  }
+}
+
+class DivergenceTest : public ::testing::Test {
+ protected:
+  std::unique_ptr<opt::IRContext> context_;
+  std::unique_ptr<DivergenceAnalysis> divergence_;
+
+  void Build(std::string text, uint32_t function_id = 1) {
+    context_ = BuildModule(SPV_ENV_UNIVERSAL_1_0, CLIMessageConsumer, text,
+                           SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+    ASSERT_NE(nullptr, context_.get());
+    opt::Module* module = context_->module();
+    ASSERT_NE(nullptr, module);
+    // First function should have the given ID.
+    ASSERT_NE(module->begin(), module->end());
+    opt::Function* function = &*module->begin();
+    ASSERT_EQ(function->result_id(), function_id);
+    divergence_.reset(new DivergenceAnalysis(*context_));
+    divergence_->Run(function);
+  }
+};
+
+// Makes assertions a bit shorter.
+using Level = DivergenceAnalysis::DivergenceLevel;
+
+namespace {
+std::string Preamble() {
+  return R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %1 "main" %x %y
+	       OpExecutionMode %1 OriginLowerLeft
+               OpDecorate %y Flat
+       %void = OpTypeVoid
+     %void_f = OpTypeFunction %void
+       %bool = OpTypeBool
+      %float = OpTypeFloat 32
+      %false = OpConstantFalse %bool
+       %true = OpConstantTrue %bool
+       %zero = OpConstant %float 0
+        %one = OpConstant %float 1
+        %x_t = OpTypePointer Input %float
+          %x = OpVariable %x_t Input
+          %y = OpVariable %x_t Input
+          %1 = OpFunction %void None %void_f
+  )";
+}
+}  // namespace
+
+TEST_F(DivergenceTest, SimpleTest) {
+  // pseudocode:
+  //     %10:
+  //     %11 = load x
+  //     if (%12 = (%11 < 0)) {
+  //       %13:
+  //       // do nothing
+  //     }
+  //     %14:
+  //     return
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %11 = OpLoad %float %x
+         %12 = OpFOrdLessThan %bool %11 %zero
+               OpSelectionMerge %14 None
+               OpBranchConditional %12 %13 %14
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+  // Control flow divergence.
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(13));
+  EXPECT_EQ(12, divergence_->GetDivergenceSource(13));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(14));
+  // Value divergence.
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(0, divergence_->GetDivergenceSource(11));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(11, divergence_->GetDivergenceSource(12));
+}
+
+TEST_F(DivergenceTest, FlowTypesTest) {
+  // pseudocode:
+  //   %10:
+  //   %11 = load x
+  //   %12 = x < 0 // data -> data
+  //   if (%12) {
+  //     %13: // data -> control
+  //     if (true) {
+  //       %14: // control -> control
+  //     }
+  //     %15:
+  //     %16 = 1
+  //   } else {
+  //     %17:
+  //     %18 = 2
+  //   }
+  //   %19:
+  //   %19 = phi(%16 from %15, %18 from %17) // control -> data
+  //   return
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %11 = OpLoad %float %x
+         %12 = OpFOrdLessThan %bool %11 %zero
+               OpSelectionMerge %19 None
+               OpBranchConditional %12 %13 %17
+         %13 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %true %14 %15
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+         %16 = OpFAdd %float %zero %zero
+               OpBranch %19
+         %17 = OpLabel
+         %18 = OpFAdd %float %zero %one
+               OpBranch %19
+         %19 = OpLabel
+         %20 = OpPhi %float %16 %15 %18 %17
+               OpReturn
+               OpFunctionEnd
+  )"));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(0, divergence_->GetDivergenceSource(11));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(11, divergence_->GetDivergenceSource(12));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(13));
+  EXPECT_EQ(12, divergence_->GetDivergenceSource(13));
+  EXPECT_EQ(10, divergence_->GetDivergenceDependenceSource(13));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(14));
+  EXPECT_EQ(13, divergence_->GetDivergenceSource(14));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(15));
+  EXPECT_EQ(12, divergence_->GetDivergenceSource(15));
+  EXPECT_EQ(10, divergence_->GetDivergenceDependenceSource(15));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(16));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(17));
+  EXPECT_EQ(12, divergence_->GetDivergenceSource(17));
+  EXPECT_EQ(10, divergence_->GetDivergenceDependenceSource(17));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(18));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(19));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(20));
+  EXPECT_TRUE(divergence_->GetDivergenceSource(20) == 15 ||
+              divergence_->GetDivergenceDependenceSource(20) == 17)
+      << "Got: " << divergence_->GetDivergenceDependenceSource(20);
+}
+
+TEST_F(DivergenceTest, ExitDependenceTest) {
+  // pseudocode:
+  //   %10:
+  //   %11 = load x
+  //   %12 = %11 < 0
+  //   %13:
+  //   do {
+  //     %14:
+  //     if (%12) {
+  //       %15:
+  //       continue;
+  //     }
+  //     %16:
+  //     %17:
+  //     continue;
+  //   } %18: while(false);
+  //   %19:
+  //   return
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %11 = OpLoad %float %x
+         %12 = OpFOrdLessThan %bool %11 %zero ; data -> data
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %19 %18 None
+               OpBranch %14
+         %14 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %12 %15 %16
+         %15 = OpLabel
+               OpBranch %18  ; continue
+         %16 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %18  ; continue
+         %18 = OpLabel
+               OpBranchConditional %false %13 %19
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(0, divergence_->GetDivergenceSource(11));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(11, divergence_->GetDivergenceSource(12));
+
+  // Since both branches continue, there's no divergent control dependence
+  // to 13.
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(13));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(14));
+
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(15));
+  EXPECT_EQ(12, divergence_->GetDivergenceSource(15));
+  EXPECT_EQ(14, divergence_->GetDivergenceDependenceSource(15));
+
+  // These two blocks are outside the if but are still control dependent.
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(16));
+  EXPECT_EQ(12, divergence_->GetDivergenceSource(16));
+  EXPECT_EQ(14, divergence_->GetDivergenceDependenceSource(16));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(17));
+  EXPECT_EQ(12, divergence_->GetDivergenceSource(17));
+  EXPECT_EQ(14, divergence_->GetDivergenceDependenceSource(17));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(18));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(19));
+}
+
+TEST_F(DivergenceTest, ReconvergencePromotionTest) {
+  // pseudocode:
+  // %10:
+  // %11 = load y
+  // %12 = %11 < 0
+  // if (%12) {
+  //   %13:
+  //   %14:
+  //   %15:
+  //   if (true) {
+  //     %16:
+  //   }
+  //   // Reconvergence *not* guaranteed as
+  //   // control is not uniform on the IG level
+  //   // at %15.
+  //   %17:
+  //   %18:
+  //   %19:
+  //   %20 = load x
+  // }
+  // %21:
+  // %22 = phi(%11, %20)
+  // return
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %11 = OpLoad %float %y
+         %12 = OpFOrdLessThan %bool %11 %zero
+               OpSelectionMerge %21 None
+               OpBranchConditional %12 %13 %21
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %true %16 %17
+         %16 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+         %20 = OpLoad %float %y
+               OpBranch %21
+         %21 = OpLabel
+         %22 = OpPhi %float %11 %10 %20 %19
+               OpReturn
+               OpFunctionEnd
+  )"));
+  ASSERT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  ASSERT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(21));
+
+  ASSERT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(11));
+  ASSERT_EQ(0, divergence_->GetDivergenceSource(11));
+  ASSERT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(12));
+  ASSERT_EQ(11, divergence_->GetDivergenceSource(12));
+  ASSERT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(13));
+  ASSERT_EQ(12, divergence_->GetDivergenceSource(13));
+  ASSERT_EQ(10, divergence_->GetDivergenceDependenceSource(13));
+  ASSERT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(14));
+  ASSERT_EQ(12, divergence_->GetDivergenceSource(14));
+  ASSERT_EQ(10, divergence_->GetDivergenceDependenceSource(14));
+  ASSERT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(15));
+  ASSERT_EQ(12, divergence_->GetDivergenceSource(15));
+  ASSERT_EQ(10, divergence_->GetDivergenceDependenceSource(15));
+  ASSERT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(16));
+  ASSERT_EQ(15, divergence_->GetDivergenceSource(16));
+
+  ASSERT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(17));
+  ASSERT_EQ(12, divergence_->GetDivergenceSource(17));
+  ASSERT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(18));
+  ASSERT_EQ(12, divergence_->GetDivergenceSource(18));
+  ASSERT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(19));
+  ASSERT_EQ(12, divergence_->GetDivergenceSource(19));
+
+  ASSERT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(20));
+  ASSERT_EQ(0, divergence_->GetDivergenceSource(20));
+  ASSERT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(22));
+  ASSERT_EQ(19, divergence_->GetDivergenceSource(22));
+  ASSERT_EQ(10, divergence_->GetDivergenceDependenceSource(15));
+}
+
+TEST_F(DivergenceTest, FunctionCallTest) {
+  // pseudocode:
+  // %2() {
+  //   %20:
+  //   %21 = load x
+  //   %22 = %21 < 0
+  //   if (%22) {
+  //     %23:
+  //     return
+  //   }
+  //   %24:
+  //   return
+  // }
+  //
+  // main() {
+  //   %10:
+  //   %11 = %2();
+  //   // Reconvergence *not* guaranteed.
+  //   %12:
+  //   return
+  // }
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %11 = OpFunctionCall %void %2
+               OpBranch %12
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+          %2 = OpFunction %void None %void_f
+         %20 = OpLabel
+         %21 = OpLoad %float %x
+         %22 = OpFOrdLessThan %bool %21 %zero
+               OpSelectionMerge %24 None
+               OpBranchConditional %22 %23 %24
+         %23 = OpLabel
+               OpReturn
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  // Conservatively assume function return value is uniform.
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(11));
+  // TODO(dongja): blocks reachable from diverging function calls should be
+  // divergent.
+  // EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(12));  // Wrong!
+}
+
+TEST_F(DivergenceTest, LateMergeTest) {
+  // pseudocode:
+  // %10:
+  // %11 = load y
+  // %12 = %11 < 0
+  // [merge: %15]
+  // if (%12) {
+  //   %13:
+  // }
+  // %14: // Reconvergence hasn't happened by here.
+  // %15:
+  // return
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %11 = OpLoad %float %x
+         %12 = OpFOrdLessThan %bool %11 %zero
+               OpSelectionMerge %15 None
+               OpBranchConditional %12 %13 %14
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(13));
+  // TODO(dongja):
+  // EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(14));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(14));  // Wrong!
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(15));
+}
+
+// The following series of tests makes sure that we find the least fixpoint.
+TEST_F(DivergenceTest, UniformFixpointTest) {
+  // pseudocode:
+  //   %10:
+  //   %20 = load x
+  //   %21 = load y
+  //   do {
+  //     %11:
+  //     %12:
+  //     %13 = phi(%zero from %11, %14 from %16)
+  //     %14 = %13 + 1
+  //     %15 = %13 < 1
+  //   } %16: while (%15)
+  //   %17:
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %20 = OpLoad %float %x
+         %21 = OpLoad %float %y
+               OpBranch %11
+         %11 = OpLabel
+         %13 = OpPhi %float %zero %10 %14 %16
+               OpLoopMerge %17 %16 None
+               OpBranch %12
+         %12 = OpLabel
+         %14 = OpFAdd %float %13 %one
+         %15 = OpFOrdLessThan %bool %13 %one
+               OpBranch %16
+         %16 = OpLabel
+               OpBranchConditional %15 %11 %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(13));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(14));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(15));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(16));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(17));
+}
+
+TEST_F(DivergenceTest, PartiallyUniformFixpointTest) {
+  // pseudocode:
+  //   %10:
+  //   %20 = load x
+  //   %21 = load y
+  //   do {
+  //     %11:
+  //     %12:
+  //     %13 = phi(%zero from %11, %14 from %16)
+  //     %14 = %13 + 1
+  //     %15 = %13 < %21
+  //   } %16: while (%15)
+  //   %17:
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %20 = OpLoad %float %x
+         %21 = OpLoad %float %y
+               OpBranch %11
+         %11 = OpLabel
+         %13 = OpPhi %float %zero %10 %14 %16
+               OpLoopMerge %17 %16 None
+               OpBranch %12
+         %12 = OpLabel
+         %14 = OpFAdd %float %13 %one
+         %15 = OpFOrdLessThan %bool %13 %21
+               OpBranch %16
+         %16 = OpLabel
+               OpBranchConditional %15 %11 %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  EXPECT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(13));
+  EXPECT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(14));
+  EXPECT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(15));
+  EXPECT_EQ(Level::kPartiallyUniform, divergence_->GetDivergenceLevel(16));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(17));
+}
+
+TEST_F(DivergenceTest, DivergentFixpointTest) {
+  // pseudocode:
+  //   %10:
+  //   %20 = load x
+  //   %21 = load y
+  //   do {
+  //     %11:
+  //     %12:
+  //     %13 = phi(%zero from %11, %14 from %16)
+  //     %14 = %13 + 1
+  //     %15 = %13 < %20
+  //   } %16: while (%15)
+  //   %17:
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %20 = OpLoad %float %x
+         %21 = OpLoad %float %y
+               OpBranch %11
+         %11 = OpLabel
+         %13 = OpPhi %float %zero %10 %14 %16
+               OpLoopMerge %17 %16 None
+               OpBranch %12
+         %12 = OpLabel
+         %14 = OpFAdd %float %13 %one
+         %15 = OpFOrdLessThan %bool %13 %20
+               OpBranch %16
+         %16 = OpLabel
+               OpBranchConditional %15 %11 %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(13));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(14));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(15));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(16));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(17));
+}
+
+TEST_F(DivergenceTest, DivergentOverridesPartiallyUniformTest) {
+  // pseudocode:
+  //   %10:
+  //   %20 = load x
+  //   %21 = load y
+  //   %11:
+  //   do {
+  //     %12:
+  //     %13 = phi(%21 from %11, %14 from %16)
+  //     %14 = %13 + 1
+  //     %15 = %13 < %20
+  //   } %16: while (%15)
+  //   %17:
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %20 = OpLoad %float %x
+         %21 = OpLoad %float %y
+               OpBranch %11
+         %11 = OpLabel
+         %13 = OpPhi %float %zero %10 %14 %16
+               OpLoopMerge %17 %16 None
+               OpBranch %12
+         %12 = OpLabel
+         %14 = OpFAdd %float %13 %one
+         %15 = OpFOrdLessThan %bool %13 %20
+               OpBranch %16
+         %16 = OpLabel
+               OpBranchConditional %15 %11 %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(13));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(14));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(15));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(16));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(17));
+}
+
+TEST_F(DivergenceTest, NestedFixpointTest) {
+  // pseudocode:
+  //   %10:
+  //   %20 = load x
+  //   %21 = load y
+  //   do {
+  //     %22:
+  //     %23:
+  //     %24 = phi(%zero from %22, %25 from %26)
+  //     %11:
+  //     do {
+  //       %12:
+  //       %13 = phi(%zero from %11, %14 from %16)
+  //       %14 = %13 + 1
+  //       %15 = %13 < %24
+  //     } %16: while (%15)
+  //     %17:
+  //     %25 = load x
+  //   } %26: while (false)
+  //   %27:
+  //   return
+  ASSERT_NO_FATAL_FAILURE(Build(Preamble() + R"(
+         %10 = OpLabel
+         %20 = OpLoad %float %x
+         %21 = OpLoad %float %y
+               OpBranch %22
+         %22 = OpLabel
+         %24 = OpPhi %float %zero %10 %25 %26
+               OpLoopMerge %27 %26 None
+               OpBranch %23
+         %23 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %13 = OpPhi %float %zero %23 %14 %16
+               OpLoopMerge %17 %16 None
+               OpBranch %12
+         %12 = OpLabel
+         %14 = OpFAdd %float %13 %one
+         %15 = OpFOrdLessThan %bool %13 %24
+               OpBranch %16
+         %16 = OpLabel
+               OpBranchConditional %15 %11 %17
+         %17 = OpLabel
+         %25 = OpLoad %float %x
+               OpBranch %26
+         %26 = OpLabel
+               OpBranchConditional %false %22 %27
+         %27 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )"));
+  // This test makes sure that divergent values flowing upward can influence the
+  // fixpoint of a loop.
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(10));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(11));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(12));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(13));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(14));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(15));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(16));
+  // Control of the outer loop is still uniform.
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(17));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(22));
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(23));
+  // Seed divergent values.
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(24));
+  EXPECT_EQ(Level::kDivergent, divergence_->GetDivergenceLevel(25));
+  // Outer loop control.
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(26));
+  // Merged.
+  EXPECT_EQ(Level::kUniform, divergence_->GetDivergenceLevel(27));
+}
+
+}  // namespace
+}  // namespace lint
+}  // namespace spvtools
diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp
index 0aec791..bc0ee05 100644
--- a/test/operand_capabilities_test.cpp
+++ b/test/operand_capabilities_test.cpp
@@ -199,7 +199,8 @@
                 CASE1(STORAGE_CLASS, StorageClassOutput, Shader),
                 CASE0(STORAGE_CLASS, StorageClassWorkgroup),
                 CASE0(STORAGE_CLASS, StorageClassCrossWorkgroup),
-                CASE1(STORAGE_CLASS, StorageClassPrivate, Shader),
+                CASE2(STORAGE_CLASS, StorageClassPrivate, Shader,
+                      VectorComputeINTEL),
                 CASE0(STORAGE_CLASS, StorageClassFunction),
                 CASE1(STORAGE_CLASS, StorageClassGeneric,
                       GenericPointer),  // Bug 14287
@@ -367,19 +368,6 @@
                 // clang-format on
             })));
 
-// See SPIR-V Section 3.15 FP Fast Math Mode
-INSTANTIATE_TEST_SUITE_P(
-    FPFastMathMode, EnumCapabilityTest,
-    Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
-            ValuesIn(std::vector<EnumCapabilityCase>{
-                CASE0(FP_FAST_MATH_MODE, FPFastMathModeMaskNone),
-                CASE1(FP_FAST_MATH_MODE, FPFastMathModeNotNaNMask, Kernel),
-                CASE1(FP_FAST_MATH_MODE, FPFastMathModeNotInfMask, Kernel),
-                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_SUITE_P(
     LinkageType, EnumCapabilityTest,
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 79cb3fc..bc44e8d 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -28,8 +28,11 @@
        compact_ids_test.cpp
        constants_test.cpp
        constant_manager_test.cpp
+       control_dependence.cpp
        convert_relaxed_to_half_test.cpp
+       convert_to_sampled_image_test.cpp
        copy_prop_array_test.cpp
+       dataflow.cpp
        dead_branch_elim_test.cpp
        dead_insert_elim_test.cpp
        dead_variable_elim_test.cpp
@@ -57,6 +60,7 @@
        inst_debug_printf_test.cpp
        instruction_list_test.cpp
        instruction_test.cpp
+       interp_fixup_test.cpp
        ir_builder.cpp
        ir_context_test.cpp
        ir_loader_test.cpp
@@ -78,8 +82,10 @@
        propagator_test.cpp
        reduce_load_size_test.cpp
        redundancy_elimination_test.cpp
+	   remove_unused_interface_variables_test.cpp
        register_liveness.cpp
        relax_float_ops_test.cpp
+       replace_desc_array_access_using_var_index_test.cpp
        replace_invalid_opc_test.cpp
        scalar_analysis.cpp
        scalar_replacement_test.cpp
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index 972e6e5..f228c8c 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -3948,12 +3948,15 @@
 OpLoopMerge %14 %15 None
 OpBranch %16
 %16 = OpLabel
-OpSwitch %13 %14 0 %17 1 %15
+OpSelectionMerge %18 None
+OpSwitch %13 %18 0 %17 1 %15
 %17 = OpLabel
 OpStore %3 %uint_1
 OpBranch %15
 %15 = OpLabel
 OpBranch %12
+%18 = OpLabel
+OpBranch %14
 %14 = OpLabel
 OpStore %3 %uint_0
 OpReturn
@@ -7060,6 +7063,287 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
 }
 
+TEST_F(AggressiveDCETest, ShaderDebugInfoKeepInFunctionElimStoreVar) {
+  // Verify that dead local variable tc and store eliminated but all
+  // in-function NonSemantic Shader debuginfo kept.
+
+  const std::string text = R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_non_semantic_info"
+          %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %g_sAniso %in_var_TEXCOORD2 %out_var_SV_Target0
+               OpExecutionMode %MainPs OriginUpperLeft
+          %7 = OpString "foo.frag"
+          %8 = OpString "PS_OUTPUT"
+          %9 = OpString "float"
+         %10 = OpString "vColor"
+         %11 = OpString "PS_INPUT"
+         %12 = OpString "vTextureCoords"
+         %13 = OpString "@type.2d.image"
+         %14 = OpString "type.2d.image"
+         %15 = OpString "Texture2D.TemplateParam"
+         %16 = OpString "src.MainPs"
+         %17 = OpString "tc"
+         %18 = OpString "ps_output"
+         %19 = OpString "i"
+         %20 = OpString "@type.sampler"
+         %21 = OpString "type.sampler"
+         %22 = OpString "g_sAniso"
+         %23 = OpString "g_tColor"
+               OpName %type_2d_image "type.2d.image"
+               OpName %g_tColor "g_tColor"
+               OpName %type_sampler "type.sampler"
+               OpName %g_sAniso "g_sAniso"
+               OpName %in_var_TEXCOORD2 "in.var.TEXCOORD2"
+               OpName %out_var_SV_Target0 "out.var.SV_Target0"
+               OpName %MainPs "MainPs"
+               OpName %PS_INPUT "PS_INPUT"
+               OpMemberName %PS_INPUT 0 "vTextureCoords"
+               OpName %param_var_i "param.var.i"
+               OpName %PS_OUTPUT "PS_OUTPUT"
+               OpMemberName %PS_OUTPUT 0 "vColor"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %in_var_TEXCOORD2 Location 0
+               OpDecorate %out_var_SV_Target0 Location 0
+               OpDecorate %g_tColor DescriptorSet 0
+               OpDecorate %g_tColor Binding 0
+               OpDecorate %g_sAniso DescriptorSet 0
+               OpDecorate %g_sAniso Binding 1
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+   %uint_128 = OpConstant %uint 128
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+     %uint_4 = OpConstant %uint 4
+     %uint_5 = OpConstant %uint 5
+     %uint_7 = OpConstant %uint 7
+     %uint_8 = OpConstant %uint 8
+    %uint_10 = OpConstant %uint 10
+    %uint_11 = OpConstant %uint 11
+    %uint_12 = OpConstant %uint 12
+    %uint_14 = OpConstant %uint 14
+    %uint_15 = OpConstant %uint 15
+    %uint_16 = OpConstant %uint 16
+    %uint_17 = OpConstant %uint 17
+    %uint_19 = OpConstant %uint 19
+    %uint_20 = OpConstant %uint 20
+    %uint_21 = OpConstant %uint 21
+    %uint_25 = OpConstant %uint 25
+    %uint_29 = OpConstant %uint 29
+    %uint_30 = OpConstant %uint 30
+    %uint_35 = OpConstant %uint 35
+    %uint_41 = OpConstant %uint 41
+    %uint_48 = OpConstant %uint 48
+    %uint_53 = OpConstant %uint 53
+    %uint_64 = OpConstant %uint 64
+         %45 = OpTypeFunction %void
+   %PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+  %PS_OUTPUT = OpTypeStruct %v4float
+         %47 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+   %g_tColor = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+   %g_sAniso = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%in_var_TEXCOORD2 = OpVariable %_ptr_Input_v2float Input
+%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
+         %51 = OpExtInst %void %1 DebugInfoNone
+         %52 = OpExtInst %void %1 DebugExpression
+         %53 = OpExtInst %void %1 DebugOperation %uint_0
+         %54 = OpExtInst %void %1 DebugExpression %53
+         %55 = OpExtInst %void %1 DebugSource %7
+         %56 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %55 %uint_5
+         %59 = OpExtInst %void %1 DebugTypeBasic %9 %uint_32 %uint_3 %uint_0
+         %60 = OpExtInst %void %1 DebugTypeVector %59 %uint_4
+         %58 = OpExtInst %void %1 DebugTypeMember %10 %60 %55 %uint_12 %uint_5 %uint_0 %uint_128 %uint_3
+         %57 = OpExtInst %void %1 DebugTypeComposite %8 %uint_1 %55 %uint_10 %uint_1 %56 %8 %uint_128 %uint_3 %58
+         %63 = OpExtInst %void %1 DebugTypeVector %59 %uint_2 
+         %62 = OpExtInst %void %1 DebugTypeMember %12 %63 %55 %uint_7 %uint_5 %uint_0 %uint_64 %uint_3
+         %61 = OpExtInst %void %1 DebugTypeComposite %11 %uint_1 %55 %uint_5 %uint_1 %56 %11 %uint_64 %uint_3 %62
+         %64 = OpExtInst %void %1 DebugTypeComposite %13 %uint_0 %55 %uint_0 %uint_0 %56 %14 %51 %uint_3
+         %67 = OpExtInst %void %1 DebugTypeFunction %uint_3 %57 %61
+         %68 = OpExtInst %void %1 DebugFunction %16 %67 %55 %uint_15 %uint_1 %56 %16 %uint_3 %uint_16
+         %69 = OpExtInst %void %1 DebugLexicalBlock %55 %uint_16 %uint_1 %68
+         %70 = OpExtInst %void %1 DebugLocalVariable %17 %63 %55 %uint_19 %uint_12 %69 %uint_4
+         %71 = OpExtInst %void %1 DebugLocalVariable %18 %57 %55 %uint_17 %uint_15 %69 %uint_4
+         %72 = OpExtInst %void %1 DebugLocalVariable %19 %61 %55 %uint_15 %uint_29 %68 %uint_4 %uint_1
+         %73 = OpExtInst %void %1 DebugTypeComposite %20 %uint_1 %55 %uint_0 %uint_0 %56 %21 %51 %uint_3
+         %74 = OpExtInst %void %1 DebugGlobalVariable %22 %73 %55 %uint_3 %uint_14 %56 %22 %g_sAniso %uint_8
+         %75 = OpExtInst %void %1 DebugGlobalVariable %23 %64 %55 %uint_1 %uint_11 %56 %23 %g_tColor %uint_8
+     %MainPs = OpFunction %void None %45
+         %76 = OpLabel
+         %78 = OpVariable %_ptr_Function_PS_OUTPUT Function
+         %79 = OpVariable %_ptr_Function_v2float Function
+         %81 = OpVariable %_ptr_Function_PS_OUTPUT Function
+%param_var_i = OpVariable %_ptr_Function_PS_INPUT Function
+         %82 = OpLoad %v2float %in_var_TEXCOORD2
+         %83 = OpCompositeConstruct %PS_INPUT %82
+               OpStore %param_var_i %83
+        %112 = OpExtInst %void %1 DebugFunctionDefinition %68 %MainPs
+        %109 = OpExtInst %void %1 DebugScope %68
+         %85 = OpExtInst %void %1 DebugDeclare %72 %param_var_i %52
+        %110 = OpExtInst %void %1 DebugScope %69
+         %87 = OpExtInst %void %1 DebugDeclare %71 %78 %52
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugFunctionDefinition %68 %MainPs
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugScope %68
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugDeclare %72 %param_var_i %52
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugScope %69
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugDeclare %71 %78 %52
+        %300 = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_17 %uint_30
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_17 %uint_30
+         %88 = OpAccessChain %_ptr_Function_v2float %param_var_i %int_0
+         %89 = OpLoad %v2float %88
+        %301 = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_12 %uint_35
+               OpStore %79 %89
+;CHECK-NOT:    OpStore %79 %89
+        %302 = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_12 %uint_35
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_12 %uint_35
+        %106 = OpExtInst %void %1 DebugValue %70 %89 %52
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugValue %70 %89 %52
+        %303 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_25 %uint_32
+         %91 = OpLoad %type_2d_image %g_tColor
+        %304 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_41 %uint_48
+         %92 = OpLoad %type_sampler %g_sAniso
+        %305 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_25 %uint_53
+         %94 = OpSampledImage %type_sampled_image %91 %92
+         %95 = OpImageSampleImplicitLod %v4float %94 %89 None
+        %306 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_5 %uint_53
+         %96 = OpAccessChain %_ptr_Function_v4float %78 %int_0
+               OpStore %96 %95
+        %307 = OpExtInst %void %1 DebugLine %55 %uint_21 %uint_21 %uint_12 %uint_20
+         %97 = OpLoad %PS_OUTPUT %78
+        %308 = OpExtInst %void %1 DebugLine %55 %uint_21 %uint_21 %uint_5 %uint_20
+               OpStore %81 %97
+        %309 = OpExtInst %void %1 DebugNoLine
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugNoLine
+        %111 = OpExtInst %void %1 DebugNoScope
+;CHECK: {{%\w+}} = OpExtInst %void %1 DebugNoScope
+        %100 = OpCompositeExtract %v4float %97 0
+               OpStore %out_var_SV_Target0 %100
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, ShaderDebugInfoGlobalDCE) {
+  // Verify that DebugGlobalVariable for eliminated private variable has
+  // variable operand replaced with DebugInfoNone.
+
+  const std::string text = R"(OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %out_var_SV_Target0 %a
+OpExecutionMode %MainPs OriginUpperLeft
+%5 = OpString "source2.hlsl"
+%24 = OpString "float"
+%29 = OpString "vColor"
+%33 = OpString "PS_OUTPUT"
+%37 = OpString "MainPs"
+%38 = OpString ""
+%42 = OpString "ps_output"
+%46 = OpString "a"
+OpName %a "a"
+OpName %out_var_SV_Target0 "out.var.SV_Target0"
+OpName %MainPs "MainPs"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpDecorate %out_var_SV_Target0 Location 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%8 = OpConstantNull %v4float
+%float_0 = OpConstant %float 0
+%10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%uint_1 = OpConstant %uint 1
+%uint_4 = OpConstant %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_3 = OpConstant %uint 3
+%uint_0 = OpConstant %uint 0
+%uint_128 = OpConstant %uint 128
+%uint_12 = OpConstant %uint 12
+%uint_8 = OpConstant %uint 8
+%uint_9 = OpConstant %uint 9
+%uint_10 = OpConstant %uint 10
+%uint_15 = OpConstant %uint 15
+%48 = OpTypeFunction %void
+%PS_OUTPUT = OpTypeStruct %v4float
+%54 = OpTypeFunction %PS_OUTPUT
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%a = OpVariable %_ptr_Private_v4float Private
+;CHECK-NOT: %a = OpVariable %_ptr_Private_v4float Private
+%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
+;CHECK: [[dbg_none:%\w+]] = OpExtInst %void %1 DebugInfoNone
+%18 = OpExtInst %void %1 DebugExpression
+%19 = OpExtInst %void %1 DebugSource %5
+%20 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %19 %uint_5
+%25 = OpExtInst %void %1 DebugTypeBasic %24 %uint_32 %uint_3 %uint_0
+%28 = OpExtInst %void %1 DebugTypeVector %25 %uint_4
+%31 = OpExtInst %void %1 DebugTypeMember %29 %28 %19 %uint_5 %uint_12 %uint_0 %uint_128 %uint_3
+%34 = OpExtInst %void %1 DebugTypeComposite %33 %uint_1 %19 %uint_3 %uint_8 %20 %33 %uint_128 %uint_3 %31
+%36 = OpExtInst %void %1 DebugTypeFunction %uint_3 %34
+%39 = OpExtInst %void %1 DebugFunction %37 %36 %19 %uint_8 %uint_1 %20 %38 %uint_3 %uint_9
+%41 = OpExtInst %void %1 DebugLexicalBlock %19 %uint_9 %uint_1 %39
+%43 = OpExtInst %void %1 DebugLocalVariable %42 %34 %19 %uint_10 %uint_15 %41 %uint_4
+%47 = OpExtInst %void %1 DebugGlobalVariable %46 %28 %19 %uint_1 %uint_15 %20 %46 %a %uint_8
+;CHECK: %47 = OpExtInst %void %1 DebugGlobalVariable %46 %28 %19 %uint_1 %uint_15 %20 %46 [[dbg_none]] %uint_8
+%MainPs = OpFunction %void None %48
+%49 = OpLabel
+%65 = OpVariable %_ptr_Function_PS_OUTPUT Function
+%66 = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpStore %a %8
+%72 = OpExtInst %void %1 DebugScope %41
+%69 = OpExtInst %void %1 DebugDeclare %43 %65 %18
+OpLine %5 11 5
+%70 = OpAccessChain %_ptr_Function_v4float %65 %int_0
+OpStore %70 %10
+OpLine %5 12 12
+%71 = OpLoad %PS_OUTPUT %65
+OpLine %5 12 5
+OpStore %66 %71
+%73 = OpExtInst %void %1 DebugNoLine
+%74 = OpExtInst %void %1 DebugNoScope
+%51 = OpLoad %PS_OUTPUT %66
+%53 = OpCompositeExtract %v4float %51 0
+OpStore %out_var_SV_Target0 %53
+OpLine %5 13 1
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
 TEST_F(AggressiveDCETest, DebugInfoDeclareKeepsStore) {
   // Verify that local variable tc and its store are kept by DebugDeclare.
   //
@@ -7581,11 +7865,178 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
 }
 
-// TODO(greg-lunarg): Add tests to verify handling of these cases:
-//
-//    Check that logical addressing required
-//    Check that function calls inhibit optimization
-//    Others?
+TEST_F(AggressiveDCETest, KeepExportFunctions) {
+  // All functions are reachable.  In particular, ExportedFunc and Constant are
+  // reachable because ExportedFunc is exported.  Nothing should be removed.
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpCapability Linkage",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\"",
+               "OpName %main \"main\"",
+               "OpName %ExportedFunc \"ExportedFunc\"",
+               "OpName %Live \"Live\"",
+               "OpDecorate %ExportedFunc LinkageAttributes \"ExportedFunc\" Export",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+       "%main = OpFunction %void None %7",
+         "%15 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+"%ExportedFunc = OpFunction %void None %7",
+         "%19 = OpLabel",
+         "%16 = OpFunctionCall %void %Live",
+               "OpReturn",
+               "OpFunctionEnd",
+  "%Live = OpFunction %void None %7",
+         "%20 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  std::string assembly = JoinAllInsts(text);
+  auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>(
+      assembly, /* skip_nop = */ true, /* do_validation = */ false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+  EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(AggressiveDCETest, KeepPrivateVarInExportFunctions) {
+  // The loads and stores from the private variable should not be removed
+  // because the functions are exported and could be called.
+  const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %privateVar "privateVar"
+OpName %ReadPrivate "ReadPrivate"
+OpName %WritePrivate "WritePrivate"
+OpName %value "value"
+OpDecorate %ReadPrivate LinkageAttributes "ReadPrivate" Export
+OpDecorate %WritePrivate LinkageAttributes "WritePrivate" Export
+%int = OpTypeInt 32 1
+%_ptr_Private_int = OpTypePointer Private %int
+%6 = OpTypeFunction %int
+%void = OpTypeVoid
+%_ptr_Function_int = OpTypePointer Function %int
+%10 = OpTypeFunction %void %_ptr_Function_int
+%privateVar = OpVariable %_ptr_Private_int Private
+%ReadPrivate = OpFunction %int None %6
+%12 = OpLabel
+%8 = OpLoad %int %privateVar
+OpReturnValue %8
+OpFunctionEnd
+%WritePrivate = OpFunction %void None %10
+%value = OpFunctionParameter %_ptr_Function_int
+%13 = OpLabel
+%14 = OpLoad %int %value
+OpStore %privateVar %14
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+  EXPECT_EQ(text, std::get<0>(result));
+}
+
+TEST_F(AggressiveDCETest, KeepLableNames) {
+  const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %WritePrivate "WritePrivate"
+OpName %entry "entry"
+OpName %target "target"
+OpDecorate %WritePrivate LinkageAttributes "WritePrivate" Export
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%WritePrivate = OpFunction %void None %3
+%entry = OpLabel
+OpBranch %target
+%target = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ false);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+  EXPECT_EQ(text, std::get<0>(result));
+}
+
+TEST_F(AggressiveDCETest, PreserveInterface) {
+  // Set preserve_interface to true. Verify that unused uniform
+  // constant in entry point interface is not eliminated.
+  const std::string text = R"(OpCapability RayTracingKHR
+OpExtension "SPV_KHR_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint RayGenerationNV %2 "main" %3 %4
+OpDecorate %3 Location 0
+OpDecorate %4 DescriptorSet 2
+OpDecorate %4 Binding 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%float = OpTypeFloat 32
+%_ptr_CallableDataNV_float = OpTypePointer CallableDataNV %float
+%3 = OpVariable %_ptr_CallableDataNV_float CallableDataNV
+%13 = OpTypeAccelerationStructureKHR
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%4 = OpVariable %_ptr_UniformConstant_13 UniformConstant
+%2 = OpFunction %void None %6
+%15 = OpLabel
+OpExecuteCallableKHR %uint_0 %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>(
+      text, /* skip_nop = */ true, /* do_validation = */ false,
+      /* preserve_interface */ true);
+  EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+  EXPECT_EQ(text, std::get<0>(result));
+}
+
+TEST_F(AggressiveDCETest, EmptyContinueWithConditionalBranch) {
+  const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%2 = OpFunction %void None %4
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %11 %12 None
+OpBranch %13
+%13 = OpLabel
+OpKill
+%12 = OpLabel
+OpBranchConditional %false %10 %10
+%11 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<AggressiveDCEPass>(text, text, false);
+}
 
 }  // namespace
 }  // namespace opt
diff --git a/test/opt/block_merge_test.cpp b/test/opt/block_merge_test.cpp
index 7381908..6903c4e 100644
--- a/test/opt/block_merge_test.cpp
+++ b/test/opt/block_merge_test.cpp
@@ -90,6 +90,63 @@
                                         true);
 }
 
+TEST_F(BlockMergeTest, BlockMergeForLinkage) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %BaseColor "BaseColor"
+OpName %bb_entry "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%8 = OpTypeFunction %v4float %_ptr_Function_v4float
+%main = OpFunction %v4float None %8
+%BaseColor = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%9 = OpLoad %v4float %BaseColor
+OpStore %v %9
+OpBranch %10
+%10 = OpLabel
+%11 = OpLoad %v4float %v
+OpBranch %12
+%12 = OpLabel
+OpReturnValue %11
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %BaseColor "BaseColor"
+OpName %bb_entry "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%8 = OpTypeFunction %v4float %_ptr_Function_v4float
+%main = OpFunction %v4float None %8
+%BaseColor = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%9 = OpLoad %v4float %BaseColor
+OpStore %v %9
+%11 = OpLoad %v4float %v
+OpReturnValue %11
+OpFunctionEnd
+)";
+  SinglePassRunAndCheck<BlockMergePass>(before, after, true, true);
+}
+
 TEST_F(BlockMergeTest, EmptyBlock) {
   // Note: SPIR-V hand edited to insert empty block
   // after two statements in main.
@@ -744,6 +801,7 @@
 ; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None
 ; CHECK-NEXT: OpBranch [[ret:%\w+]]
 ; CHECK: [[ret:%\w+]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
 ; CHECK-NEXT: OpSwitch
 ; CHECK-DAG: [[cont]] = OpLabel
 ; CHECK-DAG: [[merge]] = OpLabel
@@ -763,6 +821,7 @@
 OpLoopMerge %3 %4 None
 OpBranch %5
 %5 = OpLabel
+OpSelectionMerge %6 None
 OpSwitch %int_0 %6
 %6 = OpLabel
 OpReturn
@@ -1036,6 +1095,112 @@
   SinglePassRunAndCheck<BlockMergePass>(spirv, spirv, true, true);
 }
 
+TEST_F(BlockMergeTest, DebugMerge) {
+  // Verify merge can be done completely, cleanly and validly in presence of
+  // NonSemantic.Shader.DebugInfo.100 instructions
+  const std::string text = R"(
+; CHECK: OpLoopMerge
+; CHECK-NEXT: OpBranch
+; CHECK-NOT: OpBranch
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_COLOR %out_var_SV_TARGET
+OpExecutionMode %main OriginUpperLeft
+%5 = OpString "lexblock.hlsl"
+%20 = OpString "float"
+%32 = OpString "main"
+%33 = OpString ""
+%46 = OpString "b"
+%49 = OpString "a"
+%58 = OpString "c"
+%63 = OpString "color"
+OpName %in_var_COLOR "in.var.COLOR"
+OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+OpName %main "main"
+OpDecorate %in_var_COLOR Location 0
+OpDecorate %out_var_SV_TARGET Location 0
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%v4float = OpTypeVector %float 4
+%9 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%float_1 = OpConstant %float 1
+%13 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%uint_3 = OpConstant %uint 3
+%uint_0 = OpConstant %uint 0
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_5 = OpConstant %uint 5
+%uint_12 = OpConstant %uint 12
+%uint_13 = OpConstant %uint 13
+%uint_20 = OpConstant %uint 20
+%uint_15 = OpConstant %uint 15
+%uint_17 = OpConstant %uint 17
+%uint_16 = OpConstant %uint 16
+%uint_14 = OpConstant %uint 14
+%uint_10 = OpConstant %uint 10
+%65 = OpTypeFunction %void
+%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+%62 = OpExtInst %void %1 DebugExpression
+%22 = OpExtInst %void %1 DebugTypeBasic %20 %uint_32 %uint_3 %uint_0
+%25 = OpExtInst %void %1 DebugTypeVector %22 %uint_4
+%27 = OpExtInst %void %1 DebugTypeFunction %uint_3 %25 %25
+%28 = OpExtInst %void %1 DebugSource %5
+%29 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %28 %uint_5
+%34 = OpExtInst %void %1 DebugFunction %32 %27 %28 %uint_12 %uint_1 %29 %33 %uint_3 %uint_13
+%37 = OpExtInst %void %1 DebugLexicalBlock %28 %uint_13 %uint_1 %34
+%52 = OpExtInst %void %1 DebugLexicalBlock %28 %uint_15 %uint_12 %37
+%54 = OpExtInst %void %1 DebugLocalVariable %46 %25 %28 %uint_17 %uint_12 %52 %uint_4
+%56 = OpExtInst %void %1 DebugLocalVariable %49 %25 %28 %uint_16 %uint_12 %52 %uint_4
+%59 = OpExtInst %void %1 DebugLocalVariable %58 %25 %28 %uint_14 %uint_10 %37 %uint_4
+%64 = OpExtInst %void %1 DebugLocalVariable %63 %25 %28 %uint_12 %uint_20 %34 %uint_4 %uint_1
+%main = OpFunction %void None %65
+%66 = OpLabel
+%69 = OpLoad %v4float %in_var_COLOR
+%168 = OpExtInst %void %1 DebugValue %64 %69 %62
+%169 = OpExtInst %void %1 DebugScope %37
+OpLine %5 14 10
+%164 = OpExtInst %void %1 DebugValue %59 %9 %62
+OpLine %5 15 3
+OpBranch %150
+%150 = OpLabel
+%165 = OpPhi %v4float %9 %66 %158 %159
+%167 = OpExtInst %void %1 DebugValue %59 %165 %62
+%170 = OpExtInst %void %1 DebugScope %37
+OpLine %5 15 12
+%171 = OpExtInst %void %1 DebugNoScope
+OpLoopMerge %160 %159 None
+OpBranch %151
+%151 = OpLabel
+OpLine %5 16 12
+%162 = OpExtInst %void %1 DebugValue %56 %9 %62
+OpLine %5 17 12
+%163 = OpExtInst %void %1 DebugValue %54 %13 %62
+OpLine %5 18 15
+%158 = OpFAdd %v4float %165 %13
+OpLine %5 18 5
+%166 = OpExtInst %void %1 DebugValue %59 %158 %62
+%172 = OpExtInst %void %1 DebugScope %37
+OpLine %5 19 3
+OpBranch %159
+%159 = OpLabel
+OpLine %5 19 3
+OpBranch %150
+%160 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<BlockMergePass>(text, 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 ef73435..212e051 100644
--- a/test/opt/ccp_test.cpp
+++ b/test/opt/ccp_test.cpp
@@ -1208,6 +1208,32 @@
   auto result = SinglePassRunAndMatch<CCPPass>(text, true);
   EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
 }
+
+TEST_F(CCPTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<CCPPass>(text, text, false);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/combine_access_chains_test.cpp b/test/opt/combine_access_chains_test.cpp
index aed14c9..5be3ba6 100644
--- a/test/opt/combine_access_chains_test.cpp
+++ b/test/opt/combine_access_chains_test.cpp
@@ -768,6 +768,32 @@
   SinglePassRunAndMatch<CombineAccessChains>(text, true);
 }
 
+TEST_F(CombineAccessChainsTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<CombineAccessChains>(text, text, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/control_dependence.cpp b/test/opt/control_dependence.cpp
new file mode 100644
index 0000000..4665547
--- /dev/null
+++ b/test/opt/control_dependence.cpp
@@ -0,0 +1,306 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/control_dependence.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "gmock/gmock-matchers.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/cfg.h"
+#include "test/opt/function_utils.h"
+
+namespace spvtools {
+namespace opt {
+
+namespace {
+void GatherEdges(const ControlDependenceAnalysis& cdg,
+                 std::vector<ControlDependence>& ret) {
+  cdg.ForEachBlockLabel([&](uint32_t label) {
+    ret.reserve(ret.size() + cdg.GetDependenceTargets(label).size());
+    ret.insert(ret.end(), cdg.GetDependenceTargets(label).begin(),
+               cdg.GetDependenceTargets(label).end());
+  });
+  std::sort(ret.begin(), ret.end());
+  // Verify that reverse graph is the same.
+  std::vector<ControlDependence> reverse_edges;
+  reverse_edges.reserve(ret.size());
+  cdg.ForEachBlockLabel([&](uint32_t label) {
+    reverse_edges.insert(reverse_edges.end(),
+                         cdg.GetDependenceSources(label).begin(),
+                         cdg.GetDependenceSources(label).end());
+  });
+  std::sort(reverse_edges.begin(), reverse_edges.end());
+  ASSERT_THAT(reverse_edges, testing::ElementsAreArray(ret));
+}
+
+using ControlDependenceTest = ::testing::Test;
+
+TEST(ControlDependenceTest, DependenceSimpleCFG) {
+  const std::string text = R"(
+               OpCapability Addresses
+               OpCapability Kernel
+               OpMemoryModel Physical64 OpenCL
+               OpEntryPoint Kernel %1 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeBool
+          %5 = OpTypeInt 32 0
+          %6 = OpConstant %5 0
+          %7 = OpConstantFalse %4
+          %8 = OpConstantTrue %4
+          %9 = OpConstant %5 1
+          %1 = OpFunction %2 None %3
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpSwitch %6 %12 1 %13
+         %12 = OpLabel
+               OpBranch %14
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpBranchConditional %8 %15 %16
+         %15 = OpLabel
+               OpBranch %19
+         %16 = OpLabel
+               OpBranchConditional %8 %17 %18
+         %17 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  // CFG: (all edges pointing downward)
+  //   %10
+  //    |
+  //   %11
+  //  /   \ (R: %6 == 1, L: default)
+  // %12 %13
+  //  \   /
+  //   %14
+  // T/   \F
+  // %15  %16
+  //  | T/ |F
+  //  | %17|
+  //  |  \ |
+  //  |   %18
+  //  |  /
+  // %19
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << text << std::endl;
+  const Function* fn = spvtest::GetFunction(module, 1);
+  const BasicBlock* entry = spvtest::GetBasicBlock(fn, 10);
+  EXPECT_EQ(entry, fn->entry().get())
+      << "The entry node is not the expected one";
+
+  {
+    PostDominatorAnalysis pdom;
+    const CFG& cfg = *context->cfg();
+    pdom.InitializeTree(cfg, fn);
+    ControlDependenceAnalysis cdg;
+    cdg.ComputeControlDependenceGraph(cfg, pdom);
+
+    // Test HasBlock.
+    for (uint32_t id = 10; id <= 19; id++) {
+      EXPECT_TRUE(cdg.HasBlock(id));
+    }
+    EXPECT_TRUE(cdg.HasBlock(ControlDependenceAnalysis::kPseudoEntryBlock));
+    // Check blocks before/after valid range.
+    EXPECT_FALSE(cdg.HasBlock(5));
+    EXPECT_FALSE(cdg.HasBlock(25));
+    EXPECT_FALSE(cdg.HasBlock(UINT32_MAX));
+
+    // Test ForEachBlockLabel.
+    std::set<uint32_t> block_labels;
+    cdg.ForEachBlockLabel([&block_labels](uint32_t id) {
+      bool inserted = block_labels.insert(id).second;
+      EXPECT_TRUE(inserted);  // Should have no duplicates.
+    });
+    EXPECT_THAT(block_labels, testing::ElementsAre(0, 10, 11, 12, 13, 14, 15,
+                                                   16, 17, 18, 19));
+
+    {
+      // Test WhileEachBlockLabel.
+      uint32_t iters = 0;
+      EXPECT_TRUE(cdg.WhileEachBlockLabel([&iters](uint32_t) {
+        ++iters;
+        return true;
+      }));
+      EXPECT_EQ((uint32_t)block_labels.size(), iters);
+      iters = 0;
+      EXPECT_FALSE(cdg.WhileEachBlockLabel([&iters](uint32_t) {
+        ++iters;
+        return false;
+      }));
+      EXPECT_EQ(1, iters);
+    }
+
+    // Test IsDependent.
+    EXPECT_TRUE(cdg.IsDependent(12, 11));
+    EXPECT_TRUE(cdg.IsDependent(13, 11));
+    EXPECT_TRUE(cdg.IsDependent(15, 14));
+    EXPECT_TRUE(cdg.IsDependent(16, 14));
+    EXPECT_TRUE(cdg.IsDependent(18, 14));
+    EXPECT_TRUE(cdg.IsDependent(17, 16));
+    EXPECT_TRUE(cdg.IsDependent(10, 0));
+    EXPECT_TRUE(cdg.IsDependent(11, 0));
+    EXPECT_TRUE(cdg.IsDependent(14, 0));
+    EXPECT_TRUE(cdg.IsDependent(19, 0));
+    EXPECT_FALSE(cdg.IsDependent(14, 11));
+    EXPECT_FALSE(cdg.IsDependent(17, 14));
+    EXPECT_FALSE(cdg.IsDependent(19, 14));
+    EXPECT_FALSE(cdg.IsDependent(12, 0));
+
+    // Test GetDependenceSources/Targets.
+    std::vector<ControlDependence> edges;
+    GatherEdges(cdg, edges);
+    EXPECT_THAT(edges,
+                testing::ElementsAre(
+                    ControlDependence(0, 10), ControlDependence(0, 11, 10),
+                    ControlDependence(0, 14, 10), ControlDependence(0, 19, 10),
+                    ControlDependence(11, 12), ControlDependence(11, 13),
+                    ControlDependence(14, 15), ControlDependence(14, 16),
+                    ControlDependence(14, 18, 16), ControlDependence(16, 17)));
+
+    const uint32_t expected_condition_ids[] = {
+        0, 0, 0, 0, 6, 6, 8, 8, 8, 8,
+    };
+
+    for (uint32_t i = 0; i < edges.size(); i++) {
+      EXPECT_EQ(expected_condition_ids[i], edges[i].GetConditionID(cfg));
+    }
+  }
+}
+
+TEST(ControlDependenceTest, DependencePaperCFG) {
+  const std::string text = R"(
+               OpCapability Addresses
+               OpCapability Kernel
+               OpMemoryModel Physical64 OpenCL
+               OpEntryPoint Kernel %101 "main"
+        %102 = OpTypeVoid
+        %103 = OpTypeFunction %102
+        %104 = OpTypeBool
+        %108 = OpConstantTrue %104
+        %101 = OpFunction %102 None %103
+          %1 = OpLabel
+               OpBranch %2
+          %2 = OpLabel
+               OpBranchConditional %108 %3 %7
+          %3 = OpLabel
+               OpBranchConditional %108 %4 %5
+          %4 = OpLabel
+               OpBranch %6
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpBranch %8
+          %7 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranchConditional %108 %10 %11
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %108 %12 %9
+         %12 = OpLabel
+               OpBranchConditional %108 %13 %2
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  // CFG: (edges pointing downward if no arrow)
+  //         %1
+  //         |
+  //         %2 <----+
+  //       T/  \F    |
+  //      %3    \    |
+  //    T/  \F   \   |
+  //    %4  %5    %7 |
+  //     \  /    /   |
+  //      %6    /    |
+  //        \  /     |
+  //         %8      |
+  //         |       |
+  //         %9 <-+  |
+  //       T/  |  |  |
+  //       %10 |  |  |
+  //        \  |  |  |
+  //         %11-F+  |
+  //         T|      |
+  //         %12-F---+
+  //         T|
+  //         %13
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << text << std::endl;
+  const Function* fn = spvtest::GetFunction(module, 101);
+  const BasicBlock* entry = spvtest::GetBasicBlock(fn, 1);
+  EXPECT_EQ(entry, fn->entry().get())
+      << "The entry node is not the expected one";
+
+  {
+    PostDominatorAnalysis pdom;
+    const CFG& cfg = *context->cfg();
+    pdom.InitializeTree(cfg, fn);
+    ControlDependenceAnalysis cdg;
+    cdg.ComputeControlDependenceGraph(cfg, pdom);
+
+    std::vector<ControlDependence> edges;
+    GatherEdges(cdg, edges);
+    EXPECT_THAT(
+        edges, testing::ElementsAre(
+                   ControlDependence(0, 1), ControlDependence(0, 2, 1),
+                   ControlDependence(0, 8, 1), ControlDependence(0, 9, 1),
+                   ControlDependence(0, 11, 1), ControlDependence(0, 12, 1),
+                   ControlDependence(0, 13, 1), ControlDependence(2, 3),
+                   ControlDependence(2, 6, 3), ControlDependence(2, 7),
+                   ControlDependence(3, 4), ControlDependence(3, 5),
+                   ControlDependence(9, 10), ControlDependence(11, 9),
+                   ControlDependence(11, 11, 9), ControlDependence(12, 2),
+                   ControlDependence(12, 8, 2), ControlDependence(12, 9, 2),
+                   ControlDependence(12, 11, 2), ControlDependence(12, 12, 2)));
+
+    const uint32_t expected_condition_ids[] = {
+        0,   0,   0,   0,   0,   0,   0,   108, 108, 108,
+        108, 108, 108, 108, 108, 108, 108, 108, 108, 108,
+    };
+
+    for (uint32_t i = 0; i < edges.size(); i++) {
+      EXPECT_EQ(expected_condition_ids[i], edges[i].GetConditionID(cfg));
+    }
+  }
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/convert_relaxed_to_half_test.cpp b/test/opt/convert_relaxed_to_half_test.cpp
index c138154..3a798f7 100644
--- a/test/opt/convert_relaxed_to_half_test.cpp
+++ b/test/opt/convert_relaxed_to_half_test.cpp
@@ -204,6 +204,98 @@
                                            defs_after + func_after, true, true);
 }
 
+TEST_F(ConvertToHalfTest, ConvertToHalfForLinkage) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %type_cbuff "type.cbuff"
+OpMemberName %type_cbuff 0 "c"
+OpName %cbuff "cbuff"
+OpName %main "main"
+OpName %BaseColor "BaseColor"
+OpName %bb_entry "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+OpDecorate %cbuff DescriptorSet 0
+OpDecorate %cbuff Binding 0
+OpMemberDecorate %type_cbuff 0 Offset 0
+OpDecorate %type_cbuff Block
+OpDecorate %18 RelaxedPrecision
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%type_cbuff = OpTypeStruct %float
+%_ptr_Uniform_type_cbuff = OpTypePointer Uniform %type_cbuff
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%9 = OpTypeFunction %v4float %_ptr_Function_v4float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%cbuff = OpVariable %_ptr_Uniform_type_cbuff Uniform
+%main = OpFunction %v4float None %9
+%BaseColor = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+%16 = OpAccessChain %_ptr_Uniform_float %cbuff %int_0
+%17 = OpLoad %float %16
+%18 = OpVectorTimesScalar %v4float %14 %17
+OpStore %v %18
+%19 = OpLoad %v4float %v
+OpReturnValue %19
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+OpCapability Float16
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %type_cbuff "type.cbuff"
+OpMemberName %type_cbuff 0 "c"
+OpName %cbuff "cbuff"
+OpName %main "main"
+OpName %BaseColor "BaseColor"
+OpName %bb_entry "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+OpDecorate %cbuff DescriptorSet 0
+OpDecorate %cbuff Binding 0
+OpMemberDecorate %type_cbuff 0 Offset 0
+OpDecorate %type_cbuff Block
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%type_cbuff = OpTypeStruct %float
+%_ptr_Uniform_type_cbuff = OpTypePointer Uniform %type_cbuff
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %v4float %_ptr_Function_v4float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%cbuff = OpVariable %_ptr_Uniform_type_cbuff Uniform
+%half = OpTypeFloat 16
+%v4half = OpTypeVector %half 4
+%main = OpFunction %v4float None %14
+%BaseColor = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+%17 = OpAccessChain %_ptr_Uniform_float %cbuff %int_0
+%18 = OpLoad %float %17
+%22 = OpFConvert %v4half %16
+%23 = OpFConvert %half %18
+%7 = OpVectorTimesScalar %v4half %22 %23
+%24 = OpFConvert %v4float %7
+OpStore %v %24
+%19 = OpLoad %v4float %v
+OpReturnValue %19
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<ConvertToHalfPass>(before, after, true, true);
+}
 TEST_F(ConvertToHalfTest, ConvertToHalfWithDrefSample) {
   // The resulting SPIR-V was processed with --relax-float-ops.
   //
@@ -1331,6 +1423,72 @@
   SinglePassRunAndMatch<ConvertToHalfPass>(defs + func, true);
 }
 
+TEST_F(ConvertToHalfTest, RemoveRelaxDec) {
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/4117
+
+  // This test is a case where the relax precision decorations need to be
+  // removed, but the body of the function does not change because there are not
+  // arithmetic operations.  So, there is not need for the Float16 capability.
+  const std::string test =
+      R"(
+; CHECK-NOT: OpCapability Float16
+; GLSL seems to generate this decoration on the load of a texture, which seems odd to me.
+; This pass does not currently remove it, and I'm not sure what we should do with it, so I will leave it.
+; CHECK: OpDecorate [[tex:%\w+]] RelaxedPrecision
+; CHECK-NOT: OpDecorate {{%\w+}} RelaxedPrecision
+; CHECK: OpLabel
+; CHECK: [[tex]] = OpLoad {{%\w+}} %sTexture
+; CHECK: [[coord:%\w+]] = OpLoad %v2float
+; CHECK: [[retval:%\w+]] = OpImageSampleImplicitLod %v4float {{%\w+}} [[coord]]
+; CHECK: OpStore %outFragColor [[retval]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %outFragColor %v_texcoord
+               OpExecutionMode %main OriginUpperLeft
+               OpSource ESSL 310
+               OpName %main "main"
+               OpName %outFragColor "outFragColor"
+               OpName %sTexture "sTexture"
+               OpName %v_texcoord "v_texcoord"
+               OpDecorate %outFragColor RelaxedPrecision
+               OpDecorate %outFragColor Location 0
+               OpDecorate %sTexture RelaxedPrecision
+               OpDecorate %sTexture DescriptorSet 0
+               OpDecorate %sTexture Binding 0
+               OpDecorate %14 RelaxedPrecision
+               OpDecorate %v_texcoord RelaxedPrecision
+               OpDecorate %v_texcoord Location 0
+               OpDecorate %18 RelaxedPrecision
+               OpDecorate %19 RelaxedPrecision
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outFragColor = OpVariable %_ptr_Output_v4float Output
+         %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+         %11 = OpTypeSampledImage %10
+%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11
+   %sTexture = OpVariable %_ptr_UniformConstant_11 UniformConstant
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+ %v_texcoord = OpVariable %_ptr_Input_v2float Input
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %14 = OpLoad %11 %sTexture
+         %18 = OpLoad %v2float %v_texcoord
+         %19 = OpImageSampleImplicitLod %v4float %14 %18
+               OpStore %outFragColor %19
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto result = SinglePassRunAndMatch<ConvertToHalfPass>(test, true);
+  EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/convert_to_sampled_image_test.cpp b/test/opt/convert_to_sampled_image_test.cpp
new file mode 100644
index 0000000..37f6560
--- /dev/null
+++ b/test/opt/convert_to_sampled_image_test.cpp
@@ -0,0 +1,353 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/convert_to_sampled_image_pass.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using testing::Eq;
+using VectorOfDescriptorSetAndBindingPairs =
+    std::vector<DescriptorSetAndBinding>;
+
+struct DescriptorSetAndBindingStringParsingTestCase {
+  const char* descriptor_set_binding_str;
+  bool expect_success;
+  VectorOfDescriptorSetAndBindingPairs expected_descriptor_set_binding_pairs;
+};
+
+using DescriptorSetAndBindingStringParsingTest =
+    ::testing::TestWithParam<DescriptorSetAndBindingStringParsingTestCase>;
+
+TEST_P(DescriptorSetAndBindingStringParsingTest, TestCase) {
+  const auto& tc = GetParam();
+  auto actual_descriptor_set_binding_pairs =
+      ConvertToSampledImagePass::ParseDescriptorSetBindingPairsString(
+          tc.descriptor_set_binding_str);
+  if (tc.expect_success) {
+    EXPECT_NE(nullptr, actual_descriptor_set_binding_pairs);
+    if (actual_descriptor_set_binding_pairs) {
+      EXPECT_THAT(*actual_descriptor_set_binding_pairs,
+                  Eq(tc.expected_descriptor_set_binding_pairs));
+    }
+  } else {
+    EXPECT_EQ(nullptr, actual_descriptor_set_binding_pairs);
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidString, DescriptorSetAndBindingStringParsingTest,
+    ::testing::ValuesIn(std::vector<
+                        DescriptorSetAndBindingStringParsingTestCase>{
+        // 0. empty vector
+        {"", true, VectorOfDescriptorSetAndBindingPairs({})},
+        // 1. one pair
+        {"100:1024", true,
+         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100,
+                                                                       1024}})},
+        // 2. two pairs
+        {"100:1024 200:2048", true,
+         VectorOfDescriptorSetAndBindingPairs(
+             {DescriptorSetAndBinding{100, 1024},
+              DescriptorSetAndBinding{200, 2048}})},
+        // 3. spaces between entries
+        {"100:1024 \n \r \t \v \f 200:2048", true,
+         VectorOfDescriptorSetAndBindingPairs(
+             {DescriptorSetAndBinding{100, 1024},
+              DescriptorSetAndBinding{200, 2048}})},
+        // 4. \t, \n, \r and spaces before spec id
+        {"   \n \r\t \t \v \f 100:1024", true,
+         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100,
+                                                                       1024}})},
+        // 5. \t, \n, \r and spaces after value string
+        {"100:1024   \n \r\t \t \v \f ", true,
+         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{100,
+                                                                       1024}})},
+        // 6. maximum spec id
+        {"4294967295:0", true,
+         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{
+             4294967295, 0}})},
+        // 7. minimum spec id
+        {"0:100", true,
+         VectorOfDescriptorSetAndBindingPairs({DescriptorSetAndBinding{0,
+                                                                       100}})},
+        // 8. multiple entries
+        {"101:1 102:2 103:3 104:4 200:201 9999:1000", true,
+         VectorOfDescriptorSetAndBindingPairs(
+             {DescriptorSetAndBinding{101, 1}, DescriptorSetAndBinding{102, 2},
+              DescriptorSetAndBinding{103, 3}, DescriptorSetAndBinding{104, 4},
+              DescriptorSetAndBinding{200, 201},
+              DescriptorSetAndBinding{9999, 1000}})},
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    InvalidString, DescriptorSetAndBindingStringParsingTest,
+    ::testing::ValuesIn(
+        std::vector<DescriptorSetAndBindingStringParsingTestCase>{
+            // 0. missing default value
+            {"100:", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 1. descriptor set is not an integer
+            {"100.0:200", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 2. descriptor set is not a number
+            {"something_not_a_number:1", false,
+             VectorOfDescriptorSetAndBindingPairs{}},
+            // 3. only descriptor set number
+            {"100", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 4. empty descriptor set
+            {":3", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 5. only colon
+            {":", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 6. descriptor set overflow
+            {"4294967296:200", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 7. descriptor set less than 0
+            {"-1:200", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 8. nullptr
+            {nullptr, false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 9. only a number is invalid
+            {"1234", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 10. invalid entry separator
+            {"12:34;23:14", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 11. invalid descriptor set and default value separator
+            {"12@34", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 12. spaces before colon
+            {"100   :1024", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 13. spaces after colon
+            {"100:   1024", false, VectorOfDescriptorSetAndBindingPairs{}},
+            // 14. descriptor set represented in hex float format is invalid
+            {"0x3p10:200", false, VectorOfDescriptorSetAndBindingPairs{}},
+        }));
+
+std::string BuildShader(const char* shader_decorate_instructions,
+                        const char* shader_image_and_sampler_variables,
+                        const char* shader_body) {
+  // Base HLSL code:
+  //
+  // SamplerState sam : register(s2);
+  // Texture2D <float4> texture : register(t5);
+  //
+  // float4 main() : SV_TARGET {
+  //     return texture.SampleLevel(sam, float2(1, 2), 10, 2);
+  // }
+  std::stringstream ss;
+  ss << R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %out_var_SV_TARGET
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_sampler "type.sampler"
+               OpName %type_2d_image "type.2d.image"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %main "main"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %out_var_SV_TARGET Location 0
+               )";
+  ss << shader_decorate_instructions;
+  ss << R"(
+      %float = OpTypeFloat 32
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %v2float = OpTypeVector %float 2
+         %12 = OpConstantComposite %v2float %float_1 %float_2
+   %float_10 = OpConstant %float 10
+        %int = OpTypeInt 32 1
+      %int_2 = OpConstant %int 2
+      %v2int = OpTypeVector %int 2
+         %17 = OpConstantComposite %v2int %int_2 %int_2
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+               )";
+  ss << shader_image_and_sampler_variables;
+  ss << R"(
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %23
+         %24 = OpLabel
+  )";
+  ss << shader_body;
+  ss << R"(
+               OpReturn
+               OpFunctionEnd
+  )";
+  return ss.str();
+}
+
+using ConvertToSampledImageTest = PassTest<::testing::Test>;
+
+TEST_F(ConvertToSampledImageTest, Texture2DAndSamplerToSampledImage) {
+  const std::string shader = BuildShader(
+      R"(
+               OpDecorate %sam DescriptorSet 0
+               OpDecorate %sam Binding 5
+               OpDecorate %texture DescriptorSet 0
+               OpDecorate %texture Binding 5
+               )",
+      R"(
+            ; CHECK-NOT: OpVariable %_ptr_UniformConstant_type_2d_image
+
+            ; CHECK: [[tex:%\w+]] = OpVariable %_ptr_UniformConstant_type_sampled_image UniformConstant
+        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+               )",
+      R"(
+            ; CHECK: [[load:%\w+]] = OpLoad %type_sampled_image [[tex]]
+            ; CHECK: OpImageSampleExplicitLod %v4float [[load]]
+         %25 = OpLoad %type_2d_image %texture
+         %26 = OpLoad %type_sampler %sam
+         %27 = OpSampledImage %type_sampled_image %25 %26
+         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
+               OpStore %out_var_SV_TARGET %28
+               )");
+
+  auto result = SinglePassRunAndMatch<ConvertToSampledImagePass>(
+      shader, /* do_validate = */ true,
+      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}});
+
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
+
+TEST_F(ConvertToSampledImageTest, Texture2DToSampledImage) {
+  const std::string shader = BuildShader(
+      R"(
+               OpDecorate %sam DescriptorSet 0
+               OpDecorate %sam Binding 2
+               OpDecorate %texture DescriptorSet 0
+               OpDecorate %texture Binding 5
+               )",
+      R"(
+            ; CHECK: [[tex:%\w+]] = OpVariable %_ptr_UniformConstant_type_sampled_image UniformConstant
+        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+               )",
+      R"(
+            ; CHECK: [[load:%\w+]] = OpLoad %type_sampled_image [[tex]]
+            ; CHECK: [[image_extraction:%\w+]] = OpImage %type_2d_image [[load]]
+            ; CHECK: OpSampledImage %type_sampled_image [[image_extraction]]
+         %25 = OpLoad %type_2d_image %texture
+         %26 = OpLoad %type_sampler %sam
+         %27 = OpSampledImage %type_sampled_image %25 %26
+         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
+               OpStore %out_var_SV_TARGET %28
+               )");
+
+  auto result = SinglePassRunAndMatch<ConvertToSampledImagePass>(
+      shader, /* do_validate = */ true,
+      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}});
+
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
+
+TEST_F(ConvertToSampledImageTest, SamplerToSampledImage) {
+  const std::string shader = BuildShader(
+      R"(
+               OpDecorate %sam DescriptorSet 0
+               OpDecorate %sam Binding 2
+               OpDecorate %texture DescriptorSet 0
+               OpDecorate %texture Binding 5
+               )",
+      R"(
+        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+               )",
+      R"(
+         %25 = OpLoad %type_2d_image %texture
+         %26 = OpLoad %type_sampler %sam
+         %27 = OpSampledImage %type_sampled_image %25 %26
+         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
+               OpStore %out_var_SV_TARGET %28
+               )");
+
+  auto result = SinglePassRunToBinary<ConvertToSampledImagePass>(
+      shader, /* skip_nop = */ false,
+      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 2}});
+
+  EXPECT_EQ(std::get<1>(result), Pass::Status::Failure);
+}
+
+TEST_F(ConvertToSampledImageTest, TwoImagesWithDuplicatedDescriptorSetBinding) {
+  const std::string shader = BuildShader(
+      R"(
+               OpDecorate %sam DescriptorSet 0
+               OpDecorate %sam Binding 2
+               OpDecorate %texture0 DescriptorSet 0
+               OpDecorate %texture0 Binding 5
+               OpDecorate %texture1 DescriptorSet 0
+               OpDecorate %texture1 Binding 5
+               )",
+      R"(
+        %sam = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+   %texture0 = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+   %texture1 = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+               )",
+      R"(
+         %25 = OpLoad %type_2d_image %texture0
+         %26 = OpLoad %type_sampler %sam
+         %27 = OpSampledImage %type_sampled_image %25 %26
+         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
+               OpStore %out_var_SV_TARGET %28
+               )");
+
+  auto result = SinglePassRunToBinary<ConvertToSampledImagePass>(
+      shader, /* skip_nop = */ false,
+      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 5}});
+
+  EXPECT_EQ(std::get<1>(result), Pass::Status::Failure);
+}
+
+TEST_F(ConvertToSampledImageTest,
+       TwoSamplersWithDuplicatedDescriptorSetBinding) {
+  const std::string shader = BuildShader(
+      R"(
+               OpDecorate %sam0 DescriptorSet 0
+               OpDecorate %sam0 Binding 2
+               OpDecorate %sam1 DescriptorSet 0
+               OpDecorate %sam1 Binding 2
+               OpDecorate %texture DescriptorSet 0
+               OpDecorate %texture Binding 5
+               )",
+      R"(
+       %sam0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+       %sam1 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+    %texture = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+               )",
+      R"(
+         %25 = OpLoad %type_2d_image %texture
+         %26 = OpLoad %type_sampler %sam0
+         %27 = OpSampledImage %type_sampled_image %25 %26
+         %28 = OpImageSampleExplicitLod %v4float %27 %12 Lod|ConstOffset %float_10 %17
+               OpStore %out_var_SV_TARGET %28
+               )");
+
+  auto result = SinglePassRunToBinary<ConvertToSampledImagePass>(
+      shader, /* skip_nop = */ false,
+      VectorOfDescriptorSetAndBindingPairs{DescriptorSetAndBinding{0, 2}});
+
+  EXPECT_EQ(std::get<1>(result), Pass::Status::Failure);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/copy_prop_array_test.cpp b/test/opt/copy_prop_array_test.cpp
index 72bc7f6..a4599f0 100644
--- a/test/opt/copy_prop_array_test.cpp
+++ b/test/opt/copy_prop_array_test.cpp
@@ -1814,6 +1814,31 @@
   SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
 }
 
+TEST_F(CopyPropArrayPassTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<CopyPropagateArrays>(text, text, false);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/dataflow.cpp b/test/opt/dataflow.cpp
new file mode 100644
index 0000000..4742015
--- /dev/null
+++ b/test/opt/dataflow.cpp
@@ -0,0 +1,225 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/opt/dataflow.h"
+
+#include <map>
+#include <set>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "opt/function_utils.h"
+#include "source/opt/build_module.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using DataFlowTest = ::testing::Test;
+
+// Simple analyses for testing:
+
+// Stores the result IDs of visited instructions in visit order.
+struct VisitOrder : public ForwardDataFlowAnalysis {
+  std::vector<uint32_t> visited_result_ids;
+
+  VisitOrder(IRContext& context, LabelPosition label_position)
+      : ForwardDataFlowAnalysis(context, label_position) {}
+
+  VisitResult Visit(Instruction* inst) override {
+    if (inst->HasResultId()) {
+      visited_result_ids.push_back(inst->result_id());
+    }
+    return DataFlowAnalysis::VisitResult::kResultFixed;
+  }
+};
+
+// For each block, stores the set of blocks it can be preceded by.
+// For example, with the following CFG:
+//    V-----------.
+// -> 11 -> 12 -> 13 -> 15
+//            \-> 14 ---^
+//
+// The answer is:
+// 11: 11, 12, 13
+// 12: 11, 12, 13
+// 13: 11, 12, 13
+// 14: 11, 12, 13
+// 15: 11, 12, 13, 14
+struct BackwardReachability : public ForwardDataFlowAnalysis {
+  std::map<uint32_t, std::set<uint32_t>> reachable_from;
+
+  BackwardReachability(IRContext& context)
+      : ForwardDataFlowAnalysis(
+            context, ForwardDataFlowAnalysis::LabelPosition::kLabelsOnly) {}
+
+  VisitResult Visit(Instruction* inst) override {
+    // Conditional branches can be enqueued from labels, so skip them.
+    if (inst->opcode() != SpvOpLabel)
+      return DataFlowAnalysis::VisitResult::kResultFixed;
+    uint32_t id = inst->result_id();
+    VisitResult ret = DataFlowAnalysis::VisitResult::kResultFixed;
+    std::set<uint32_t>& precedents = reachable_from[id];
+    for (uint32_t pred : context().cfg()->preds(id)) {
+      bool pred_inserted = precedents.insert(pred).second;
+      if (pred_inserted) {
+        ret = DataFlowAnalysis::VisitResult::kResultChanged;
+      }
+      for (uint32_t block : reachable_from[pred]) {
+        bool inserted = precedents.insert(block).second;
+        if (inserted) {
+          ret = DataFlowAnalysis::VisitResult::kResultChanged;
+        }
+      }
+    }
+    return ret;
+  }
+
+  void InitializeWorklist(Function* function,
+                          bool is_first_iteration) override {
+    // Since successor function is exact, only need one pass.
+    if (is_first_iteration) {
+      ForwardDataFlowAnalysis::InitializeWorklist(function, true);
+    }
+  }
+};
+
+TEST_F(DataFlowTest, ReversePostOrder) {
+  // Note: labels and IDs are intentionally out of order.
+  //
+  // CFG: (order of branches is from bottom to top)
+  //          V-----------.
+  // -> 50 -> 40 -> 20 -> 60 -> 70
+  //            \-> 30 ---^
+
+  // DFS tree with RPO numbering:
+  // -> 50[0] -> 40[1] -> 20[2]    60[4] -> 70[5]
+  //                  \-> 30[3] ---^
+
+  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
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %6 = OpTypeBool
+          %5 = OpConstantTrue %6
+          %2 = OpFunction %3 None %4
+         %50 = OpLabel
+         %51 = OpUndef %6
+         %52 = OpUndef %6
+               OpBranch %40
+         %70 = OpLabel
+         %69 = OpUndef %6
+               OpReturn
+         %60 = OpLabel
+         %61 = OpUndef %6
+               OpBranchConditional %5 %70 %40
+         %30 = OpLabel
+         %29 = OpUndef %6
+               OpBranch %60
+         %20 = OpLabel
+         %21 = OpUndef %6
+               OpBranch %60
+         %40 = OpLabel
+         %39 = OpUndef %6
+               OpBranchConditional %5 %30 %20
+               OpFunctionEnd
+  )";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(context, nullptr);
+
+  Function* function = spvtest::GetFunction(context->module(), 2);
+
+  std::map<ForwardDataFlowAnalysis::LabelPosition, std::vector<uint32_t>>
+      expected_order;
+  expected_order[ForwardDataFlowAnalysis::LabelPosition::kLabelsOnly] = {
+      50, 40, 20, 30, 60, 70,
+  };
+  expected_order[ForwardDataFlowAnalysis::LabelPosition::kLabelsAtBeginning] = {
+      50, 51, 52, 40, 39, 20, 21, 30, 29, 60, 61, 70, 69,
+  };
+  expected_order[ForwardDataFlowAnalysis::LabelPosition::kLabelsAtEnd] = {
+      51, 52, 50, 39, 40, 21, 20, 29, 30, 61, 60, 69, 70,
+  };
+  expected_order[ForwardDataFlowAnalysis::LabelPosition::kNoLabels] = {
+      51, 52, 39, 21, 29, 61, 69,
+  };
+
+  for (const auto& test_case : expected_order) {
+    VisitOrder analysis(*context, test_case.first);
+    analysis.Run(function);
+    EXPECT_EQ(test_case.second, analysis.visited_result_ids);
+  }
+}
+
+TEST_F(DataFlowTest, BackwardReachability) {
+  // CFG:
+  //    V-----------.
+  // -> 11 -> 12 -> 13 -> 15
+  //            \-> 14 ---^
+
+  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
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %6 = OpTypeBool
+          %5 = OpConstantTrue %6
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %5 %14 %13
+         %13 = OpLabel
+               OpBranchConditional %5 %15 %11
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(context, nullptr);
+
+  Function* function = spvtest::GetFunction(context->module(), 2);
+
+  BackwardReachability analysis(*context);
+  analysis.Run(function);
+
+  std::map<uint32_t, std::set<uint32_t>> expected_result;
+  expected_result[11] = {11, 12, 13};
+  expected_result[12] = {11, 12, 13};
+  expected_result[13] = {11, 12, 13};
+  expected_result[14] = {11, 12, 13};
+  expected_result[15] = {11, 12, 13, 14};
+  EXPECT_EQ(expected_result, analysis.reachable_from);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index 41ce31d..9c1ef2a 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -2722,6 +2722,7 @@
 ; CHECK: [[bb1]] = OpLabel
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
 ; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
@@ -2739,6 +2740,7 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
+OpSelectionMerge %bb3 None
 OpSwitch %undef_int %bb3 0 %loop_merge 1 %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
@@ -2782,6 +2784,7 @@
 ; CHECK: [[bb1]] = OpLabel
 ; CHECK-NEXT: OpBranch [[bb2:%\w+]]
 ; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
 ; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_cont]] 1 [[bb3:%\w+]]
 ; CHECK: [[bb3]] = OpLabel
 ; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
@@ -2799,6 +2802,7 @@
 OpSelectionMerge %sel_merge None
 OpBranchConditional %true %bb2 %bb4
 %bb2 = OpLabel
+OpSelectionMerge %bb3 None
 OpSwitch %undef_int %bb3 0 %cont 1 %bb3
 %bb3 = OpLabel
 OpBranch %sel_merge
@@ -3399,6 +3403,71 @@
   SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
 }
 
+TEST_F(DeadBranchElimTest, DontTransferDecorations) {
+  // When replacing %4 with %14, we don't want %14 to inherit %4's decorations.
+  const std::string text = R"(
+; CHECK-NOT: OpDecorate {{%\w+}} RelaxedPrecision
+; CHECK: [[div:%\w+]] = OpFDiv
+; CHECK: {{%\w+}} = OpCopyObject %float [[div]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+          %3 = OpString "STEVEN"
+               OpDecorate %4 RelaxedPrecision
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+       %void = OpTypeVoid
+    %float_1 = OpConstant %float 1
+     %uint_0 = OpConstant %uint 0
+         %10 = OpTypeFunction %void
+          %2 = OpFunction %void None %10
+         %11 = OpLabel
+               OpSelectionMerge %12 None
+               OpSwitch %uint_0 %13
+         %13 = OpLabel
+         %14 = OpFDiv %float %float_1 %float_1
+               OpLine %3 0 0
+               OpBranch %12
+         %15 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+          %4 = OpPhi %float %float_1 %15 %14 %13
+         %16 = OpCopyObject %float %4
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadBranchElimPass>(text, true);
+}
+
+TEST_F(DeadBranchElimTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<DeadBranchElimPass>(text, text, false);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    More complex control flow
diff --git a/test/opt/dead_insert_elim_test.cpp b/test/opt/dead_insert_elim_test.cpp
index 9ea948a..268e659 100644
--- a/test/opt/dead_insert_elim_test.cpp
+++ b/test/opt/dead_insert_elim_test.cpp
@@ -170,6 +170,72 @@
                                             after_predefs + after, true, true);
 }
 
+TEST_F(DeadInsertElimTest, DeadInsertForLinkage) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %BaseColor "BaseColor"
+OpName %bb_entry "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%14 = OpTypeFunction %v2float %_ptr_Function_v2float
+%_ptr_Function_float = OpTypePointer Function %float
+%main = OpFunction %v2float None %14
+%BaseColor = OpFunctionParameter %_ptr_Function_v2float
+%bb_entry = OpLabel
+%v = OpVariable %_ptr_Function_v2float Function
+%16 = OpLoad %v2float %v
+%17 = OpAccessChain %_ptr_Function_float %BaseColor %int_1
+%18 = OpLoad %float %17
+%19 = OpCompositeInsert %v2float %18 %16 0
+%20 = OpCompositeInsert %v2float %float_0 %19 0
+OpReturnValue %20
+OpFunctionEnd
+)";
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %BaseColor "BaseColor"
+OpName %bb_entry "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%14 = OpTypeFunction %v2float %_ptr_Function_v2float
+%_ptr_Function_float = OpTypePointer Function %float
+%main = OpFunction %v2float None %14
+%BaseColor = OpFunctionParameter %_ptr_Function_v2float
+%bb_entry = OpLabel
+%v = OpVariable %_ptr_Function_v2float Function
+%16 = OpLoad %v2float %v
+%20 = OpCompositeInsert %v2float %float_0 %16 0
+OpReturnValue %20
+OpFunctionEnd
+)";
+  SinglePassRunAndCheck<DeadInsertElimPass>(before, after, true, true);
+}
+
 TEST_F(DeadInsertElimTest, DeadInsertInChainWithPhi) {
   // Dead insert eliminated with phi in insertion chain.
   //
diff --git a/test/opt/debug_info_manager_test.cpp b/test/opt/debug_info_manager_test.cpp
index 911331a..e87d0be 100644
--- a/test/opt/debug_info_manager_test.cpp
+++ b/test/opt/debug_info_manager_test.cpp
@@ -31,6 +31,9 @@
 static const uint32_t kDebugInlinedAtOperandLineIndex = 4;
 static const uint32_t kDebugInlinedAtOperandScopeIndex = 5;
 static const uint32_t kDebugInlinedAtOperandInlinedIndex = 6;
+static const uint32_t kOpLineInOperandFileIndex = 0;
+static const uint32_t kOpLineInOperandLineIndex = 1;
+static const uint32_t kOpLineInOperandColumnIndex = 2;
 
 namespace spvtools {
 namespace opt {
@@ -182,6 +185,122 @@
       100U);
 }
 
+TEST(DebugInfoManager, CreateDebugInlinedAtWithConstantManager) {
+  // Show that CreateDebugInlinedAt will use the Constant manager to generate
+  // its line operand if the Constant and DefUse managers are valid. This is
+  // proven by checking that the id for the line operand 7 is the same as the
+  // existing constant 7.
+  //
+  // int function1() {
+  //   return 1;
+  // }
+  //
+  // void main() {
+  //   function1();
+  // }
+  const std::string text = R"(OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%3 = OpString "parent3.hlsl"
+%8 = OpString "int"
+%19 = OpString "function1"
+%20 = OpString ""
+%26 = OpString "main"
+OpName %main "main"
+OpName %src_main "src.main"
+OpName %bb_entry "bb.entry"
+OpName %function1 "function1"
+OpName %bb_entry_0 "bb.entry"
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%void = OpTypeVoid
+%uint_4 = OpConstant %uint 4
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_1 = OpConstant %uint 1
+%uint_5 = OpConstant %uint 5
+%uint_2 = OpConstant %uint 2
+%uint_17 = OpConstant %uint 17
+%uint_6 = OpConstant %uint 6
+%uint_13 = OpConstant %uint 13
+%100 = OpConstant %uint 7
+%31 = OpTypeFunction %void
+%42 = OpTypeFunction %int
+%10 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 %uint_4 %uint_0
+%13 = OpExtInst %void %1 DebugTypeFunction %uint_3 %10
+%15 = OpExtInst %void %1 DebugSource %3
+%16 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %15 %uint_5
+%21 = OpExtInst %void %1 DebugFunction %19 %13 %15 %uint_2 %uint_1 %16 %20 %uint_3 %uint_2
+%23 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_2 %uint_17 %21
+%25 = OpExtInst %void %1 DebugTypeFunction %uint_3 %void
+%27 = OpExtInst %void %1 DebugFunction %26 %25 %15 %uint_6 %uint_1 %16 %20 %uint_3 %uint_6
+%29 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_6 %uint_13 %27
+%main = OpFunction %void None %31
+%32 = OpLabel
+%33 = OpFunctionCall %void %src_main
+OpLine %3 8 1
+OpReturn
+OpFunctionEnd
+OpLine %3 6 1
+%src_main = OpFunction %void None %31
+OpNoLine
+%bb_entry = OpLabel
+%47 = OpExtInst %void %1 DebugScope %27
+%37 = OpExtInst %void %1 DebugFunctionDefinition %27 %src_main
+%48 = OpExtInst %void %1 DebugScope %29
+OpLine %3 7 3
+%39 = OpFunctionCall %int %function1
+%49 = OpExtInst %void %1 DebugScope %27
+OpLine %3 8 1
+OpReturn
+%50 = OpExtInst %void %1 DebugNoScope
+OpFunctionEnd
+OpLine %3 2 1
+%function1 = OpFunction %int None %42
+OpNoLine
+%bb_entry_0 = OpLabel
+%51 = OpExtInst %void %1 DebugScope %21
+%45 = OpExtInst %void %1 DebugFunctionDefinition %21 %function1
+%52 = OpExtInst %void %1 DebugScope %23
+OpLine %3 3 3
+OpReturnValue %int_1
+%53 = OpExtInst %void %1 DebugNoScope
+OpFunctionEnd
+  )";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  const uint32_t line_number = 7U;
+  Instruction line(context.get(), SpvOpLine);
+  line.SetInOperands({
+      {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {5U}},
+      {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {line_number}},
+      {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {0U}},
+  });
+
+  DebugScope scope(29U, 0U);
+
+  auto db_manager = context.get()->get_debug_info_mgr();
+  auto du_manager = context.get()->get_def_use_mgr();
+  auto c_manager = context.get()->get_constant_mgr();
+
+  (void)du_manager;
+  (void)c_manager;
+
+  uint32_t inlined_at_id = db_manager->CreateDebugInlinedAt(&line, scope);
+  auto* inlined_at = db_manager->GetDebugInlinedAt(inlined_at_id);
+  EXPECT_NE(inlined_at, nullptr);
+  EXPECT_EQ(inlined_at->GetSingleWordOperand(kDebugInlinedAtOperandLineIndex),
+            100);
+}
+
 TEST(DebugInfoManager, GetDebugInfoNone) {
   const std::string text = R"(
                OpCapability Shader
@@ -609,6 +728,80 @@
   EXPECT_FALSE(dbg_info_mgr->IsVariableDebugDeclared(100));
 }
 
+TEST(DebugInfoManager, AddDebugValueForDecl) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in_var_COLOR
+               OpExecutionMode %main OriginUpperLeft
+          %5 = OpString "ps.hlsl"
+         %14 = OpString "#line 1 \"ps.hlsl\"
+void main(float in_var_color : COLOR) {
+  float color = in_var_color;
+}
+"
+         %17 = OpString "float"
+         %21 = OpString "main"
+         %24 = OpString "color"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %main "main"
+               OpDecorate %in_var_COLOR Location 0
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+       %void = OpTypeVoid
+         %27 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+%in_var_COLOR = OpVariable %_ptr_Input_float Input
+         %13 = OpExtInst %void %1 DebugExpression
+         %15 = OpExtInst %void %1 DebugSource %5 %14
+         %16 = OpExtInst %void %1 DebugCompilationUnit 1 4 %15 HLSL
+         %18 = OpExtInst %void %1 DebugTypeBasic %17 %uint_32 Float
+         %20 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %18 %18
+         %22 = OpExtInst %void %1 DebugFunction %21 %20 %15 1 1 %16 %21 FlagIsProtected|FlagIsPrivate 1 %main
+         %12 = OpExtInst %void %1 DebugInfoNone
+         %25 = OpExtInst %void %1 DebugLocalVariable %24 %18 %15 1 20 %22 FlagIsLocal 0
+       %main = OpFunction %void None %27
+         %28 = OpLabel
+        %100 = OpVariable %_ptr_Function_float Function
+         %31 = OpLoad %float %in_var_COLOR
+        %101 = OpExtInst %void %1 DebugScope %22
+               OpLine %5 13 7
+               OpStore %100 %31
+               OpNoLine
+        %102 = OpExtInst %void %1 DebugNoScope
+         %36 = OpExtInst %void %1 DebugDeclare %25 %100 %13
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto* def_use_mgr = context->get_def_use_mgr();
+  auto* dbg_decl = def_use_mgr->GetDef(36);
+  EXPECT_EQ(dbg_decl->GetOpenCL100DebugOpcode(),
+            OpenCLDebugInfo100DebugDeclare);
+
+  auto* dbg_info_mgr = context->get_debug_info_mgr();
+  Instruction* store = dbg_decl->PreviousNode();
+  auto* dbg_value =
+      dbg_info_mgr->AddDebugValueForDecl(dbg_decl, 100, dbg_decl, store);
+
+  EXPECT_EQ(dbg_value->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue);
+  EXPECT_EQ(dbg_value->dbg_line_inst()->GetSingleWordInOperand(
+                kOpLineInOperandFileIndex),
+            5);
+  EXPECT_EQ(dbg_value->dbg_line_inst()->GetSingleWordInOperand(
+                kOpLineInOperandLineIndex),
+            13);
+  EXPECT_EQ(dbg_value->dbg_line_inst()->GetSingleWordInOperand(
+                kOpLineInOperandColumnIndex),
+            7);
+}
+
 }  // namespace
 }  // namespace analysis
 }  // namespace opt
diff --git a/test/opt/desc_sroa_test.cpp b/test/opt/desc_sroa_test.cpp
index b35ad47..dcb625d 100644
--- a/test/opt/desc_sroa_test.cpp
+++ b/test/opt/desc_sroa_test.cpp
@@ -770,6 +770,69 @@
   SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
 }
 
+TEST_F(DescriptorScalarReplacementTest, MemberDecorationForResourceStruct) {
+  // Check that an OpMemberDecorate instruction is correctly converted to a
+  // OpDecorate instruction.
+
+  const std::string shader = R"(
+; CHECK: OpDecorate [[t:%\w+]] DescriptorSet 0
+; CHECK: OpDecorate [[t]] Binding 0
+; CHECK: OpDecorate [[t]] RelaxedPrecision
+; CHECK: OpDecorate [[s:%\w+]] DescriptorSet 0
+; CHECK: OpDecorate [[s]] Binding 1
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %PSMain "PSMain" %in_var_TEXCOORD %out_var_SV_Target
+               OpExecutionMode %PSMain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %sampler2D_h "sampler2D_h"
+               OpMemberName %sampler2D_h 0 "t"
+               OpMemberName %sampler2D_h 1 "s"
+               OpName %type_2d_image "type.2d.image"
+               OpName %type_sampler "type.sampler"
+               OpName %_MainTex "_MainTex"
+               OpName %in_var_TEXCOORD "in.var.TEXCOORD"
+               OpName %out_var_SV_Target "out.var.SV_Target"
+               OpName %PSMain "PSMain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %in_var_TEXCOORD Location 0
+               OpDecorate %out_var_SV_Target Location 0
+               OpDecorate %_MainTex DescriptorSet 0
+               OpDecorate %_MainTex Binding 0
+               OpMemberDecorate %sampler2D_h 0 RelaxedPrecision
+               OpDecorate %out_var_SV_Target RelaxedPrecision
+               OpDecorate %69 RelaxedPrecision
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%type_sampler = OpTypeSampler
+%sampler2D_h = OpTypeStruct %type_2d_image %type_sampler
+%_ptr_UniformConstant_sampler2D_h = OpTypePointer UniformConstant %sampler2D_h
+    %v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %35 = OpTypeFunction %void
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %_MainTex = OpVariable %_ptr_UniformConstant_sampler2D_h UniformConstant
+%in_var_TEXCOORD = OpVariable %_ptr_Input_v2float Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+     %PSMain = OpFunction %void None %35
+         %43 = OpLabel
+         %44 = OpLoad %v2float %in_var_TEXCOORD
+         %57 = OpLoad %sampler2D_h %_MainTex
+         %72 = OpCompositeExtract %type_2d_image %57 0
+         %73 = OpCompositeExtract %type_sampler %57 1
+         %68 = OpSampledImage %type_sampled_image %72 %73
+         %69 = OpImageSampleImplicitLod %v4float %68 %44 None
+               OpStore %out_var_SV_Target %69
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/eliminate_dead_functions_test.cpp b/test/opt/eliminate_dead_functions_test.cpp
index 96ecdc6..96deb2a 100644
--- a/test/opt/eliminate_dead_functions_test.cpp
+++ b/test/opt/eliminate_dead_functions_test.cpp
@@ -439,6 +439,84 @@
   SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true);
 }
 
+TEST_F(EliminateDeadFunctionsBasicTest, NonSemanticInfoRemoveDebugPrintf) {
+  const std::string text = R"(
+; CHECK-NOT: %foo_ = OpFunction %void None % 3
+; CHECK-NOT: % 7 = OpLabel
+; CHECK-NOT: %c = OpVariable %_ptr_Function_v4float Function
+; CHECK-NOT: % 22 = OpAccessChain %_ptr_UniformConstant_13 %samplers %int_0
+; CHECK-NOT: % 23 = OpLoad % 13 % 22
+; CHECK-NOT: % 27 = OpImageSampleExplicitLod %v4float % 23 % 26 Lod %float_0
+; CHECK-NOT: OpStore %c % 27
+; CHECK-NOT: % 31 = OpAccessChain %_ptr_Function_float %c %uint_0
+; CHECK-NOT: % 32 = OpLoad %float %31
+; CHECK-NOT: % 34 = OpExtInst %void %33 1 % 28 % 32
+OpCapability RayTracingKHR
+OpExtension "SPV_KHR_non_semantic_info"
+OpExtension "SPV_KHR_ray_tracing"
+%1 = OpExtInstImport "GLSL.std.450"
+%33 = OpExtInstImport "NonSemantic.DebugPrintf"
+OpMemoryModel Logical GLSL450
+OpEntryPoint ClosestHitNV %main "main" %samplers
+%28 = OpString "%f"
+OpSource GLSL 460
+OpSourceExtension "GL_EXT_debug_printf"
+OpName %main "main"
+OpName %foo_ "foo("
+OpName %c "c"
+OpName %samplers "samplers"
+OpDecorate %samplers DescriptorSet 0
+OpDecorate %samplers Binding 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%12 = OpTypeImage %float 3D 0 0 0 1 Unknown
+%13 = OpTypeSampledImage %12
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_13_uint_1 = OpTypeArray %13 %uint_1
+%_ptr_UniformConstant__arr_13_uint_1 = OpTypePointer UniformConstant %_arr_13_uint_1
+%samplers = OpVariable %_ptr_UniformConstant__arr_13_uint_1 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%v3float = OpTypeVector %float 3
+%float_0 = OpConstant %float 0
+%26 = OpConstantComposite %v3float %float_0 %float_0 %float_0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%main = OpFunction %void None %3
+%5 = OpLabel
+%36 = OpVariable %_ptr_Function_v4float Function
+%38 = OpAccessChain %_ptr_UniformConstant_13 %samplers %int_0
+%39 = OpLoad %13 %38
+%40 = OpImageSampleExplicitLod %v4float %39 %26 Lod %float_0
+OpStore %36 %40
+%41 = OpAccessChain %_ptr_Function_float %36 %uint_0
+%42 = OpLoad %float %41
+%43 = OpExtInst %void %33 1 %28 %42
+OpReturn
+OpFunctionEnd
+%foo_ = OpFunction %void None %3
+%7 = OpLabel
+%c = OpVariable %_ptr_Function_v4float Function
+%22 = OpAccessChain %_ptr_UniformConstant_13 %samplers %int_0
+%23 = OpLoad %13 %22
+%27 = OpImageSampleExplicitLod %v4float %23 %26 Lod %float_0
+OpStore %c %27
+%31 = OpAccessChain %_ptr_Function_float %c %uint_0
+%32 = OpLoad %float %31
+%34 = OpExtInst %void %33 1 %28 %32
+OpReturn
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/eliminate_dead_member_test.cpp b/test/opt/eliminate_dead_member_test.cpp
index 7728782..e277999 100644
--- a/test/opt/eliminate_dead_member_test.cpp
+++ b/test/opt/eliminate_dead_member_test.cpp
@@ -576,7 +576,6 @@
    %_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
@@ -627,6 +626,67 @@
   EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
 }
 
+TEST_F(EliminateDeadMemberTest, KeepStorageBufferMembers) {
+  // Test that all members of the storage buffer struct %S are kept.
+  // No change expected.
+  const std::string text = R"(
+               OpCapability Shader
+               OpExtension "SPV_GOOGLE_hlsl_functionality1"
+               OpExtension "SPV_GOOGLE_user_type"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %PSMain "PSMain" %out_var_SV_TARGET
+               OpExecutionMode %PSMain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_StructuredBuffer_S "type.StructuredBuffer.S"
+               OpName %S "S"
+               OpMemberName %S 0 "A"
+               OpMemberName %S 1 "B"
+               OpName %Buf "Buf"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %PSMain "PSMain"
+               OpDecorateString %out_var_SV_TARGET UserSemantic "SV_TARGET"
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %Buf DescriptorSet 0
+               OpDecorate %Buf Binding 0
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 16
+               OpDecorate %_runtimearr_S ArrayStride 32
+               OpMemberDecorate %type_StructuredBuffer_S 0 Offset 0
+               OpMemberDecorate %type_StructuredBuffer_S 0 NonWritable
+               OpDecorate %type_StructuredBuffer_S BufferBlock
+               OpDecorateString %Buf UserTypeGOOGLE "structuredbuffer"
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+      %int_1 = OpConstant %int 1
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+          %S = OpTypeStruct %v4float %v4float
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_StructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_StructuredBuffer_S = OpTypePointer Uniform %type_StructuredBuffer_S
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+        %Buf = OpVariable %_ptr_Uniform_type_StructuredBuffer_S Uniform
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+     %PSMain = OpFunction %void None %18
+         %20 = OpLabel
+         %21 = OpAccessChain %_ptr_Uniform_v4float %Buf %int_0 %uint_0 %int_1
+         %22 = OpLoad %v4float %21
+               OpStore %out_var_SV_TARGET %22
+               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.
diff --git a/test/opt/fix_storage_class_test.cpp b/test/opt/fix_storage_class_test.cpp
index 4c8504a..1c0101a 100644
--- a/test/opt/fix_storage_class_test.cpp
+++ b/test/opt/fix_storage_class_test.cpp
@@ -528,6 +528,48 @@
   SinglePassRunAndMatch<FixStorageClass>(text, false);
 }
 
+TEST_F(FixStorageClassTest, AllowImageFormatMismatch) {
+  const std::string text = R"(OpCapability Shader
+OpCapability SampledBuffer
+OpCapability ImageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpSource HLSL 600
+OpName %type_buffer_image "type.buffer.image"
+OpName %Buf "Buf"
+OpName %main "main"
+OpName %src_main "src.main"
+OpName %bb_entry "bb.entry"
+OpName %type_buffer_image_0 "type.buffer.image"
+OpName %b "b"
+OpDecorate %Buf DescriptorSet 0
+OpDecorate %Buf Binding 0
+%float = OpTypeFloat 32
+%type_buffer_image = OpTypeImage %float Buffer 2 0 0 2 Rgba16f
+%_ptr_UniformConstant_type_buffer_image = OpTypePointer UniformConstant %type_buffer_image
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%type_buffer_image_0 = OpTypeImage %float Buffer 2 0 0 2 Rgba32f
+%_ptr_Function_type_buffer_image_0 = OpTypePointer Function %type_buffer_image_0
+%Buf = OpVariable %_ptr_UniformConstant_type_buffer_image UniformConstant
+%main = OpFunction %void None %11
+%13 = OpLabel
+%14 = OpFunctionCall %void %src_main
+OpReturn
+OpFunctionEnd
+%src_main = OpFunction %void None %11
+%bb_entry = OpLabel
+%b = OpVariable %_ptr_Function_type_buffer_image_0 Function
+%15 = OpLoad %type_buffer_image %Buf
+OpStore %b %15
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<FixStorageClass>(text, text, false, false);
+}
+
 using FixTypeTest = PassTest<::testing::Test>;
 
 TEST_F(FixTypeTest, FixAccessChain) {
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index bb6098c..0487e78 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -137,10 +137,12 @@
 %int = OpTypeInt 32 1
 %long = OpTypeInt 64 1
 %uint = OpTypeInt 32 0
+%ulong = OpTypeInt 64 0
 %v2int = OpTypeVector %int 2
 %v4int = OpTypeVector %int 4
 %v4float = OpTypeVector %float 4
 %v4double = OpTypeVector %double 4
+%v2uint = OpTypeVector %uint 2
 %v2float = OpTypeVector %float 2
 %v2double = OpTypeVector %double 2
 %v2half = OpTypeVector %half 2
@@ -153,6 +155,7 @@
 %_ptr_double = OpTypePointer Function %double
 %_ptr_half = OpTypePointer Function %half
 %_ptr_long = OpTypePointer Function %long
+%_ptr_ulong = OpTypePointer Function %ulong
 %_ptr_v2int = OpTypePointer Function %v2int
 %_ptr_v4int = OpTypePointer Function %v4int
 %_ptr_v4float = OpTypePointer Function %v4float
@@ -170,12 +173,23 @@
 %int_2 = OpConstant %int 2
 %int_3 = OpConstant %int 3
 %int_4 = OpConstant %int 4
+%int_10 = OpConstant %int 10
+%int_1073741824 = OpConstant %int 1073741824
+%int_n1 = OpConstant %int -1
 %int_n24 = OpConstant %int -24
+%int_n858993459 = OpConstant %int -858993459
 %int_min = OpConstant %int -2147483648
 %int_max = OpConstant %int 2147483647
 %long_0 = OpConstant %long 0
+%long_1 = OpConstant %long 1
 %long_2 = OpConstant %long 2
 %long_3 = OpConstant %long 3
+%long_10 = OpConstant %long 10
+%long_4611686018427387904 = OpConstant %long 4611686018427387904
+%long_n1 = OpConstant %long -1
+%long_n3689348814741910323 = OpConstant %long -3689348814741910323
+%long_min = OpConstant %long -9223372036854775808
+%long_max = OpConstant %long 9223372036854775807
 %uint_0 = OpConstant %uint 0
 %uint_1 = OpConstant %uint 1
 %uint_2 = OpConstant %uint 2
@@ -183,7 +197,13 @@
 %uint_4 = OpConstant %uint 4
 %uint_32 = OpConstant %uint 32
 %uint_42 = OpConstant %uint 42
+%uint_2147483649 = OpConstant %uint 2147483649
 %uint_max = OpConstant %uint 4294967295
+%ulong_0 = OpConstant %ulong 0
+%ulong_1 = OpConstant %ulong 1
+%ulong_2 = OpConstant %ulong 2
+%ulong_9223372036854775809 = OpConstant %ulong 9223372036854775809
+%ulong_max = OpConstant %ulong 18446744073709551615
 %v2int_undef = OpUndef %v2int
 %v2int_0_0 = OpConstantComposite %v2int %int_0 %int_0
 %v2int_1_0 = OpConstantComposite %v2int %int_1 %int_0
@@ -191,6 +211,7 @@
 %v2int_2_3 = OpConstantComposite %v2int %int_2 %int_3
 %v2int_3_2 = OpConstantComposite %v2int %int_3 %int_2
 %v2int_4_4 = OpConstantComposite %v2int %int_4 %int_4
+%v2int_min_max = OpConstantComposite %v2int %int_min %int_max
 %v2bool_null = OpConstantNull %v2bool
 %v2bool_true_false = OpConstantComposite %v2bool %true %false
 %v2bool_false_true = OpConstantComposite %v2bool %false %true
@@ -258,6 +279,15 @@
 %v4double_1_1_1_0p5 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_0p5
 %v4double_null = OpConstantNull %v4double
 %v4float_n1_2_1_3 = OpConstantComposite %v4float %float_n1 %float_2 %float_1 %float_3
+%uint_0x3f800000 = OpConstant %uint 0x3f800000
+%uint_0xbf800000 = OpConstant %uint 0xbf800000
+%v2uint_0x3f800000_0xbf800000 = OpConstantComposite %v2uint %uint_0x3f800000 %uint_0xbf800000
+%long_0xbf8000003f800000 = OpConstant %long 0xbf8000003f800000
+%int_0x3FF00000 = OpConstant %int 0x3FF00000
+%int_0x00000000 = OpConstant %int 0x00000000
+%int_0xC05FD666 = OpConstant %int 0xC05FD666
+%int_0x66666666 = OpConstant %int 0x66666666
+%v4int_0x3FF00000_0x00000000_0xC05FD666_0x66666666 = OpConstantComposite %v4int %int_0x00000000 %int_0x3FF00000 %int_0x66666666 %int_0xC05FD666
 )";
 
   return header;
@@ -708,7 +738,31 @@
           "%2 = OpExtInst %uint %1 UClamp %uint_2 %undef %uint_1\n" +
           "OpReturn\n" +
           "OpFunctionEnd",
-      2, 1)
+      2, 1),
+    // Test case 46: Bit-cast int 0 to unsigned int
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %uint %int_0\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, 0),
+    // Test case 47: Bit-cast int -24 to unsigned int
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %uint %int_n24\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, static_cast<uint32_t>(-24)),
+    // Test case 48: Bit-cast float 1.0f to unsigned int
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpBitcast %uint %float_1\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        2, static_cast<uint32_t>(0x3f800000))
 ));
 // clang-format on
 
@@ -790,10 +844,72 @@
           "%2 = OpVectorShuffle %v2int %v2int_null %v2int_2_3 0 4294967295 \n" +
           "OpReturn\n" +
           "OpFunctionEnd",
-      2, {0,0})
+      2, {0,0}),
+    // Test case 4: fold bit-cast int -24 to unsigned int
+    InstructionFoldingCase<std::vector<uint32_t>>(
+      Header() + "%main = OpFunction %void None %void_func\n" +
+          "%main_lab = OpLabel\n" +
+          "%n = OpVariable %_ptr_int Function\n" +
+          "%load = OpLoad %int %n\n" +
+          "%2 = OpBitcast %v2uint %v2int_min_max\n" +
+          "OpReturn\n" +
+          "OpFunctionEnd",
+      2, {2147483648, 2147483647})
 ));
 // clang-format on
 
+using DoubleVectorInstructionFoldingTest =
+    ::testing::TestWithParam<InstructionFoldingCase<std::vector<double>>>;
+
+TEST_P(DoubleVectorInstructionFoldingTest, 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.
+  analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+  Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+  bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+  // Make sure the instruction folded as expected.
+  EXPECT_TRUE(succeeded);
+  if (succeeded && inst != nullptr) {
+    EXPECT_EQ(inst->opcode(), SpvOpCopyObject);
+    inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
+    std::vector<SpvOp> opcodes = {SpvOpConstantComposite};
+    EXPECT_THAT(opcodes, Contains(inst->opcode()));
+    analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+    const analysis::Constant* result = const_mrg->GetConstantFromInst(inst);
+    EXPECT_NE(result, nullptr);
+    if (result != nullptr) {
+      const std::vector<const analysis::Constant*>& componenets =
+          result->AsVectorConstant()->GetComponents();
+      EXPECT_EQ(componenets.size(), tc.expected_result.size());
+      for (size_t i = 0; i < componenets.size(); i++) {
+        EXPECT_EQ(tc.expected_result[i], componenets[i]->GetDouble());
+      }
+    }
+  }
+}
+
+// clang-format off
+INSTANTIATE_TEST_SUITE_P(TestCase, DoubleVectorInstructionFoldingTest,
+::testing::Values(
+   // Test case 0: bit-cast int {0x3FF00000,0x00000000,0xC05FD666,0x66666666}
+   //              to double vector
+   InstructionFoldingCase<std::vector<double>>(
+       Header() + "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%2 = OpBitcast %v2double %v4int_0x3FF00000_0x00000000_0xC05FD666_0x66666666\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       2, {1.0,-127.35})
+));
+
 using FloatVectorInstructionFoldingTest =
     ::testing::TestWithParam<InstructionFoldingCase<std::vector<float>>>;
 
@@ -843,7 +959,24 @@
            "%2 = OpExtInst %v2float %1 FMix %v2float_2_3 %v2float_0_0 %v2float_0p2_0p5\n" +
            "OpReturn\n" +
            "OpFunctionEnd",
-       2, {1.6f,1.5f})
+       2, {1.6f,1.5f}),
+   // Test case 1: bit-cast unsigned int vector {0x3f800000, 0xbf800000} to
+   //              float vector
+   InstructionFoldingCase<std::vector<float>>(
+       Header() + "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%2 = OpBitcast %v2float %v2uint_0x3f800000_0xbf800000\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       2, {1.0f,-1.0f}),
+   // Test case 2: bit-cast long int 0xbf8000003f800000 to float vector
+   InstructionFoldingCase<std::vector<float>>(
+       Header() + "%main = OpFunction %void None %void_func\n" +
+           "%main_lab = OpLabel\n" +
+           "%2 = OpBitcast %v2float %long_0xbf8000003f800000\n" +
+           "OpReturn\n" +
+           "OpFunctionEnd",
+       2, {1.0f,-1.0f})
 ));
 // clang-format on
 using BooleanInstructionFoldingTest =
@@ -3475,7 +3608,19 @@
             "%4 = OpCompositeExtract %int %3 2\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        4, INT_0_ID)
+        4, INT_0_ID),
+    // Test case 15:
+    // Don't fold extract fed by construct with vector result if the index is
+    // past the last element.
+    InstructionFoldingCase<uint32_t>(
+        Header() + "%main = OpFunction %void None %void_func\n" +
+            "%main_lab = OpLabel\n" +
+            "%2 = OpCompositeConstruct %v2int %int_0 %int_0\n" +
+            "%3 = OpCompositeConstruct %v4int %2 %100 %int_0\n" +
+            "%4 = OpCompositeExtract %int %3 4\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        4, 0)
 ));
 
 INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
@@ -5458,7 +5603,109 @@
         "%5 = OpFMul %float %4 %2\n" +
         "OpReturn\n" +
         "OpFunctionEnd\n",
-    5, true)
+    5, true),
+  // Test case 25: fold overflowing signed 32 bit imuls
+  // (x * 1073741824) * 2 = x * int_min
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" +
+      "; CHECK: [[int_min:%\\w+]] = OpConstant [[int]] -2147483648\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+      "; CHECK: %4 = OpIMul [[int]] [[ld]] [[int_min]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_int Function\n" +
+      "%2 = OpLoad %int %var\n" +
+      "%3 = OpIMul %int %2 %int_1073741824\n" +
+      "%4 = OpIMul %int %3 %int_2\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 26: fold overflowing signed 64 bit imuls
+  // (x * 4611686018427387904) * 2 = x * long_min
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" +
+      "; CHECK: [[long_min:%\\w+]] = OpConstant [[long]] -9223372036854775808\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+      "; CHECK: %4 = OpIMul [[long]] [[ld]] [[long_min]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_long Function\n" +
+      "%2 = OpLoad %long %var\n" +
+      "%3 = OpIMul %long %2 %long_4611686018427387904\n" +
+      "%4 = OpIMul %long %3 %long_2\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 27: fold overflowing 32 bit unsigned imuls
+  // (x * 2147483649) * 2 = x * 2
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[uint:%\\w+]] = OpTypeInt 32 0\n" +
+      "; CHECK: [[uint_2:%\\w+]] = OpConstant [[uint]] 2\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[uint]]\n" +
+      "; CHECK: %4 = OpIMul [[uint]] [[ld]] [[uint_2]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_uint Function\n" +
+      "%2 = OpLoad %uint %var\n" +
+      "%3 = OpIMul %uint %2 %uint_2147483649\n" +
+      "%4 = OpIMul %uint %3 %uint_2\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 28: fold overflowing 64 bit unsigned imuls
+  // (x * 9223372036854775809) * 2 = x * 2
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[ulong:%\\w+]] = OpTypeInt 64 0\n" +
+      "; CHECK: [[ulong_2:%\\w+]] = OpConstant [[ulong]] 2\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[ulong]]\n" +
+      "; CHECK: %4 = OpIMul [[ulong]] [[ld]] [[ulong_2]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_ulong Function\n" +
+      "%2 = OpLoad %ulong %var\n" +
+      "%3 = OpIMul %ulong %2 %ulong_9223372036854775809\n" +
+      "%4 = OpIMul %ulong %3 %ulong_2\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 29: fold underflowing signed 32 bit imuls
+  // (x * (-858993459)) * 10 = x * 2
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" +
+      "; CHECK: [[int_2:%\\w+]] = OpConstant [[int]] 2\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+      "; CHECK: %4 = OpIMul [[int]] [[ld]] [[int_2]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_int Function\n" +
+      "%2 = OpLoad %int %var\n" +
+      "%3 = OpIMul %int %2 %int_n858993459\n" +
+      "%4 = OpIMul %int %3 %int_10\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 30: fold underflowing signed 64 bit imuls
+  // (x * (-3689348814741910323)) * 10 = x * 2
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" +
+      "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+      "; CHECK: %4 = OpIMul [[long]] [[ld]] [[long_2]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_long Function\n" +
+      "%2 = OpLoad %long %var\n" +
+      "%3 = OpIMul %long %2 %long_n3689348814741910323\n" +
+      "%4 = OpIMul %long %3 %long_10\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true)
 ));
 
 INSTANTIATE_TEST_SUITE_P(MergeDivTest, MatchingInstructionFoldingTest,
@@ -5618,15 +5865,11 @@
       "OpReturn\n" +
       "OpFunctionEnd\n",
     4, false),
-  // Test case 11: merge sdiv of snegate
-  // (-x) / 2 = x / -2
+  // Test case 11: Do not merge sdiv of snegate.  If %2 is INT_MIN, then the
+  // sign of %3 will be the same as %2.  This cannot be accounted for in OpSDiv.
+  // Specifically, (-INT_MIN) / 2 != INT_MIN / -2.
   InstructionFoldingCase<bool>(
     Header() +
-      "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
-      "; CHECK: OpConstant [[int]] -2147483648\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" +
       "%main_lab = OpLabel\n" +
       "%var = OpVariable %_ptr_int Function\n" +
@@ -5635,16 +5878,12 @@
       "%4 = OpSDiv %int %3 %int_2\n" +
       "OpReturn\n" +
       "OpFunctionEnd\n",
-    4, true),
-  // Test case 12: merge sdiv of snegate
-  // 2 / (-x) = -2 / x
+    4, false),
+  // Test case 12: Do not merge sdiv of snegate.  If %2 is INT_MIN, then the
+  // sign of %3 will be the same as %2.  This cannot be accounted for in OpSDiv.
+  // Specifically, 2 / (-INT_MIN) != -2 / INT_MIN.
   InstructionFoldingCase<bool>(
     Header() +
-      "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
-      "; CHECK: OpConstant [[int]] -2147483648\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" +
       "%main_lab = OpLabel\n" +
       "%var = OpVariable %_ptr_int Function\n" +
@@ -5653,7 +5892,7 @@
       "%4 = OpSDiv %int %int_2 %3\n" +
       "OpReturn\n" +
       "OpFunctionEnd\n",
-    4, true),
+    4, false),
   // Test case 13: Don't merge
   // (x / {null}) / {null}
   InstructionFoldingCase<bool>(
@@ -5704,7 +5943,33 @@
         "%5 = OpFDiv %float %4 %2\n" +
         "OpReturn\n" +
         "OpFunctionEnd\n",
-    5, true)
+    5, true),
+  // Test case 16: Do not merge udiv of snegate
+  // (-x) / 2u
+  InstructionFoldingCase<bool>(
+    Header() +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_uint Function\n" +
+      "%2 = OpLoad %uint %var\n" +
+      "%3 = OpSNegate %uint %2\n" +
+      "%4 = OpUDiv %uint %3 %uint_2\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, false),
+  // Test case 17: Do not merge udiv of snegate
+  // 2u / (-x)
+  InstructionFoldingCase<bool>(
+    Header() +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_uint Function\n" +
+      "%2 = OpLoad %uint %var\n" +
+      "%3 = OpSNegate %uint %2\n" +
+      "%4 = OpUDiv %uint %uint_2 %3\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, false)
 ));
 
 INSTANTIATE_TEST_SUITE_P(MergeAddTest, MatchingInstructionFoldingTest,
@@ -5912,6 +6177,108 @@
       "%4 = OpFAdd %float %float_2 %3\n" +
       "OpReturn\n" +
       "OpFunctionEnd\n",
+    4, true),
+  // Test case 12: fold overflowing signed 32 bit iadds
+  // (x + int_max) + 1 = x + int_min
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" +
+      "; CHECK: [[int_min:%\\w+]] = OpConstant [[int]] -2147483648\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+      "; CHECK: %4 = OpIAdd [[int]] [[ld]] [[int_min]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_int Function\n" +
+      "%2 = OpLoad %int %var\n" +
+      "%3 = OpIAdd %int %2 %int_max\n" +
+      "%4 = OpIAdd %int %3 %int_1\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 13: fold overflowing signed 64 bit iadds
+  // (x + long_max) + 1 = x + long_min
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" +
+      "; CHECK: [[long_min:%\\w+]] = OpConstant [[long]] -9223372036854775808\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+      "; CHECK: %4 = OpIAdd [[long]] [[ld]] [[long_min]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_long Function\n" +
+      "%2 = OpLoad %long %var\n" +
+      "%3 = OpIAdd %long %2 %long_max\n" +
+      "%4 = OpIAdd %long %3 %long_1\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 14: fold overflowing 32 bit unsigned iadds
+  // (x + uint_max) + 2 = x + 1
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[uint:%\\w+]] = OpTypeInt 32 0\n" +
+      "; CHECK: [[uint_1:%\\w+]] = OpConstant [[uint]] 1\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[uint]]\n" +
+      "; CHECK: %4 = OpIAdd [[uint]] [[ld]] [[uint_1]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_uint Function\n" +
+      "%2 = OpLoad %uint %var\n" +
+      "%3 = OpIAdd %uint %2 %uint_max\n" +
+      "%4 = OpIAdd %uint %3 %uint_2\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 15: fold overflowing 64 bit unsigned iadds
+  // (x + ulong_max) + 2 = x + 1
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[ulong:%\\w+]] = OpTypeInt 64 0\n" +
+      "; CHECK: [[ulong_1:%\\w+]] = OpConstant [[ulong]] 1\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[ulong]]\n" +
+      "; CHECK: %4 = OpIAdd [[ulong]] [[ld]] [[ulong_1]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_ulong Function\n" +
+      "%2 = OpLoad %ulong %var\n" +
+      "%3 = OpIAdd %ulong %2 %ulong_max\n" +
+      "%4 = OpIAdd %ulong %3 %ulong_2\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 16: fold underflowing signed 32 bit iadds
+  // (x + int_min) + (-1) = x + int_max
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" +
+      "; CHECK: [[int_max:%\\w+]] = OpConstant [[int]] 2147483647\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+      "; CHECK: %4 = OpIAdd [[int]] [[ld]] [[int_max]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_int Function\n" +
+      "%2 = OpLoad %int %var\n" +
+      "%3 = OpIAdd %int %2 %int_min\n" +
+      "%4 = OpIAdd %int %3 %int_n1\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 17: fold underflowing signed 64 bit iadds
+  // (x + long_min) + (-1) = x + long_max
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" +
+      "; CHECK: [[long_max:%\\w+]] = OpConstant [[long]] 9223372036854775807\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+      "; CHECK: %4 = OpIAdd [[long]] [[ld]] [[long_max]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_long Function\n" +
+      "%2 = OpLoad %long %var\n" +
+      "%3 = OpIAdd %long %2 %long_min\n" +
+      "%4 = OpIAdd %long %3 %long_n1\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
     4, true)
 ));
 
@@ -6280,6 +6647,40 @@
       "%4 = OpISub %int %int_2 %3\n" +
       "OpReturn\n" +
       "OpFunctionEnd\n",
+    4, true),
+  // Test case 14: fold overflowing signed 32 bit isubs
+  // (x - int_max) - 1 = x - int_min
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" +
+      "; CHECK: [[int_min:%\\w+]] = OpConstant [[int]] -2147483648\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+      "; CHECK: %4 = OpISub [[int]] [[ld]] [[int_min]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_int Function\n" +
+      "%2 = OpLoad %int %var\n" +
+      "%3 = OpISub %int %2 %int_max\n" +
+      "%4 = OpISub %int %3 %int_1\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
+    4, true),
+  // Test case 15: fold overflowing signed 64 bit isubs
+  // (x - long_max) - 1 = x - long_min
+  InstructionFoldingCase<bool>(
+    Header() +
+      "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" +
+      "; CHECK: [[long_min:%\\w+]] = OpConstant [[long]] -9223372036854775808\n" +
+      "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+      "; CHECK: %4 = OpISub [[long]] [[ld]] [[long_min]]\n" +
+      "%main = OpFunction %void None %void_func\n" +
+      "%main_lab = OpLabel\n" +
+      "%var = OpVariable %_ptr_long Function\n" +
+      "%2 = OpLoad %long %var\n" +
+      "%3 = OpISub %long %2 %long_max\n" +
+      "%4 = OpISub %long %3 %long_1\n" +
+      "OpReturn\n" +
+      "OpFunctionEnd\n",
     4, true)
 ));
 
diff --git a/test/opt/graphics_robust_access_test.cpp b/test/opt/graphics_robust_access_test.cpp
index 4b2cd44..3c23347 100644
--- a/test/opt/graphics_robust_access_test.cpp
+++ b/test/opt/graphics_robust_access_test.cpp
@@ -1548,6 +1548,105 @@
                                                   true);
 }
 
+TEST_F(GraphicsRobustAccessTest, ReplaceIndexReportsChanged) {
+  // A ClusterFuzz generated shader that triggered a
+  // "Binary size unexpectedly changed despite the optimizer saying there was no
+  // change" assertion.
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/4166.
+  std::string shader = R"(
+; SPIR-V
+; Version: 1.0
+; Generator: Google Shaderc over Glslang; 245
+; Bound: 41
+; Schema: 0
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "else" %gl_GlobalInvocationID
+               OpExecutionMode %main LocalSize 1 1 3338665985
+               OpSource GLSL 450
+               OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+               OpSourceExtension "GL_GOOGLE_include_directive"
+               OpName %main "main"
+               OpName %index "index"
+               OpName %gl_GlobalInvocationID "gl_GlobalInvocationID"
+               OpName %S "S"
+               OpMemberName %_struct_24 0 ""
+               OpMemberName %_struct_24 1 ""
+               OpName %Dst "Dst"
+               OpMemberName %Dst 0 "s"
+               OpName %dst "dst"
+               OpName %Src "Src"
+               OpMemberName %Src 0 "s"
+               OpName %src "src"
+               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+               OpMemberDecorate %_struct_24 0 Offset 64
+               OpMemberDecorate %_struct_24 1 Offset 8
+               OpDecorate %_arr__struct_24_uint_1 ArrayStride 16
+               OpMemberDecorate %Dst 0 Offset 0
+               OpDecorate %Dst BufferBlock
+               OpDecorate %dst DescriptorSet 0
+               OpDecorate %dst Binding 1
+               OpDecorate %_arr__struct_24_uint_1_0 ArrayStride 16
+               OpMemberDecorate %Src 0 Offset 0
+               OpDecorate %Src Block
+               OpDecorate %src DescriptorSet 0
+               OpDecorate %src Binding 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+%_ptr_Function_uint = OpTypePointer Function %uint
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+  %uint_4864 = OpConstant %uint 4864
+%_ptr_Input_uint = OpTypePointer Input %uint
+     %uint_1 = OpConstant %uint 1
+       %bool = OpTypeBool
+     %v2uint = OpTypeVector %uint 2
+ %_struct_24 = OpTypeStruct %_ptr_Input_uint %v2uint
+%_arr__struct_24_uint_1 = OpTypeArray %_struct_24 %uint_1
+        %Dst = OpTypeStruct %_arr__struct_24_uint_1
+%_ptr_Uniform_Dst = OpTypePointer Uniform %Dst
+        %dst = OpVariable %_ptr_Uniform_Dst Uniform
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+%_arr__struct_24_uint_1_0 = OpTypeArray %_struct_24 %uint_1
+        %Src = OpTypeStruct %_arr__struct_24_uint_1_0
+%_ptr_Uniform_Src = OpTypePointer Uniform %Src
+        %src = OpVariable %_ptr_Uniform_Src Uniform
+%_ptr_Uniform__struct_24 = OpTypePointer Uniform %_struct_24
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+      %index = OpVariable %_ptr_Function_uint Function
+         %14 = OpAccessChain %_ptr_Input_uint %gl_GlobalInvocationID %uint_4864
+         %15 = OpLoad %uint %14
+               OpStore %index %15
+         %16 = OpLoad %uint %index
+          %S = OpUGreaterThanEqual %bool %16 %uint_1
+               OpSelectionMerge %21 None
+               OpBranchConditional %S %20 %21
+         %20 = OpLabel
+               OpReturn
+         %21 = OpLabel
+         %31 = OpLoad %uint %index
+         %36 = OpLoad %uint %index
+         %38 = OpAccessChain %_ptr_Uniform__struct_24 %src %int_0 %36
+         %39 = OpLoad %_struct_24 %38
+         %40 = OpAccessChain %_ptr_Uniform__struct_24 %dst %int_0 %31
+               OpStore %40 %39
+               OpReturn
+               OpFunctionEnd
+)";
+
+  std::vector<uint32_t> optimized_bin;
+  auto status = spvtools::opt::Pass::Status::Failure;
+  std::tie(optimized_bin, status) =
+      SinglePassRunToBinary<GraphicsRobustAccessPass>(shader, false);
+  // Check whether the pass returns the correct modification indication.
+  EXPECT_EQ(status, spvtools::opt::Pass::Status::SuccessWithChange);
+}
+
 // TODO(dneto): Test access chain index wider than 64 bits?
 // TODO(dneto): Test struct access chain index wider than 64 bits?
 // TODO(dneto): OpImageTexelPointer
diff --git a/test/opt/inline_opaque_test.cpp b/test/opt/inline_opaque_test.cpp
index 8cb8925..e4db432 100644
--- a/test/opt/inline_opaque_test.cpp
+++ b/test/opt/inline_opaque_test.cpp
@@ -226,6 +226,115 @@
       predefs + before + post_defs, predefs + after + post_defs, true, true);
 }
 
+TEST_F(InlineOpaqueTest, InlineOpaqueForLinkage) {
+  const std::string predefs_1 =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpMemberName %S_t 2 "smp"
+OpName %foo_struct_S_t_vf2_vf21_ "foo(struct-S_t-vf2-vf21;"
+OpName %s "s"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %s0 "s0"
+OpName %texCoords "texCoords"
+OpName %param "param"
+OpDecorate %main LinkageAttributes "main" Export
+)";
+
+  const std::string name = R"(OpName %return_value "return_value"
+)";
+
+  const std::string predefs_2 = R"(OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%19 = OpTypeSampledImage %18
+%S_t = OpTypeStruct %v2float %v2float %19
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%21 = OpTypeFunction %void %_ptr_Function_S_t
+%_ptr_UniformConstant_19 = OpTypePointer UniformConstant %19
+%_ptr_Function_19 = OpTypePointer Function %19
+%sampler15 = OpVariable %_ptr_UniformConstant_19 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %13
+%29 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%30 = OpLoad %v2float %texCoords
+%31 = OpAccessChain %_ptr_Function_v2float %s0 %int_0
+OpStore %31 %30
+%32 = OpLoad %19 %sampler15
+%33 = OpAccessChain %_ptr_Function_19 %s0 %int_2
+OpStore %33 %32
+%34 = OpLoad %S_t %s0
+OpStore %param %34
+%return_value = OpFunctionCall %void %foo_struct_S_t_vf2_vf21_ %param
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %13
+%29 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%30 = OpLoad %v2float %texCoords
+%31 = OpAccessChain %_ptr_Function_v2float %s0 %int_0
+OpStore %31 %30
+%32 = OpLoad %19 %sampler15
+%33 = OpAccessChain %_ptr_Function_19 %s0 %int_2
+OpStore %33 %32
+%34 = OpLoad %S_t %s0
+OpStore %param %34
+%42 = OpAccessChain %_ptr_Function_19 %param %int_2
+%43 = OpLoad %19 %42
+%44 = OpAccessChain %_ptr_Function_v2float %param %int_0
+%45 = OpLoad %v2float %44
+%46 = OpImageSampleImplicitLod %v4float %43 %45
+OpStore %outColor %46
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string post_defs =
+      R"(%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %21
+%s = OpFunctionParameter %_ptr_Function_S_t
+%35 = OpLabel
+%36 = OpAccessChain %_ptr_Function_19 %s %int_2
+%37 = OpLoad %19 %36
+%38 = OpAccessChain %_ptr_Function_v2float %s %int_0
+%39 = OpLoad %v2float %38
+%40 = OpImageSampleImplicitLod %v4float %37 %39
+OpStore %outColor %40
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<InlineOpaquePass>(
+      predefs_1 + name + predefs_2 + before + post_defs,
+      predefs_1 + predefs_2 + after + post_defs, true, true);
+}
+
 TEST_F(InlineOpaqueTest, InlineInNonEntryPointFunction) {
   // This demonstrates opaque inlining in a function that is not
   // an entry point function (main2) but is in the call tree of an
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index 2939901..d22f027 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -2300,7 +2300,6 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %1 "main"
 OpExecutionMode %1 OriginUpperLeft
-OpDecorate %2 DescriptorSet 439418829
 %void = OpTypeVoid
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -2330,7 +2329,6 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %1 "main"
 OpExecutionMode %1 OriginUpperLeft
-OpDecorate %2 DescriptorSet 439418829
 %void = OpTypeVoid
 %4 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -2581,6 +2579,132 @@
   SinglePassRunAndCheck<InlineExhaustivePass>(before, after, false, true);
 }
 
+TEST_F(InlineTest, InlineForLinkage) {
+  const std::string before =
+      R"(OpCapability SampledBuffer
+OpCapability ImageBuffer
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %type_buffer_image "type.buffer.image"
+OpName %output "output"
+OpName %main "main"
+OpName %color "color"
+OpName %bb_entry "bb.entry"
+OpName %param_var_color "param.var.color"
+OpName %fn "fn"
+OpName %color_0 "color"
+OpName %bb_entry_0 "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+OpDecorate %output DescriptorSet 0
+OpDecorate %output Binding 1
+%float = OpTypeFloat 32
+%float_0_200000003 = OpConstant %float 0.200000003
+%v4float = OpTypeVector %float 4
+%6 = OpConstantComposite %v4float %float_0_200000003 %float_0_200000003 %float_0_200000003 %float_0_200000003
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%type_buffer_image = OpTypeImage %float Buffer 2 0 0 2 Rgba32f
+%_ptr_UniformConstant_type_buffer_image = OpTypePointer UniformConstant %type_buffer_image
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%11 = OpTypeFunction %float %_ptr_Function_v4float
+%_ptr_Function_float = OpTypePointer Function %float
+%output = OpVariable %_ptr_UniformConstant_type_buffer_image UniformConstant
+%main = OpFunction %float None %11
+%color = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry = OpLabel
+%param_var_color = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %color
+OpStore %param_var_color %16
+%17 = OpFunctionCall %float %fn %param_var_color
+OpReturnValue %17
+OpFunctionEnd
+%fn = OpFunction %float None %11
+%color_0 = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry_0 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %color_0
+OpStore %v %22
+%23 = OpLoad %v4float %v
+%24 = OpFMul %v4float %23 %6
+OpStore %v %24
+%26 = OpAccessChain %_ptr_Function_float %v %int_0
+%27 = OpLoad %float %26
+OpReturnValue %27
+OpFunctionEnd
+      )";
+
+  const std::string after =
+      R"(OpCapability SampledBuffer
+OpCapability ImageBuffer
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %type_buffer_image "type.buffer.image"
+OpName %output "output"
+OpName %main "main"
+OpName %color "color"
+OpName %bb_entry "bb.entry"
+OpName %param_var_color "param.var.color"
+OpName %fn "fn"
+OpName %color_0 "color"
+OpName %bb_entry_0 "bb.entry"
+OpName %v "v"
+OpDecorate %main LinkageAttributes "main" Export
+OpDecorate %output DescriptorSet 0
+OpDecorate %output Binding 1
+%float = OpTypeFloat 32
+%float_0_200000003 = OpConstant %float 0.200000003
+%v4float = OpTypeVector %float 4
+%6 = OpConstantComposite %v4float %float_0_200000003 %float_0_200000003 %float_0_200000003 %float_0_200000003
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%type_buffer_image = OpTypeImage %float Buffer 2 0 0 2 Rgba32f
+%_ptr_UniformConstant_type_buffer_image = OpTypePointer UniformConstant %type_buffer_image
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%11 = OpTypeFunction %float %_ptr_Function_v4float
+%_ptr_Function_float = OpTypePointer Function %float
+%output = OpVariable %_ptr_UniformConstant_type_buffer_image UniformConstant
+%main = OpFunction %float None %11
+%color = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry = OpLabel
+%28 = OpVariable %_ptr_Function_v4float Function
+%29 = OpVariable %_ptr_Function_float Function
+%param_var_color = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %color
+OpStore %param_var_color %16
+%31 = OpLoad %v4float %param_var_color
+OpStore %28 %31
+%32 = OpLoad %v4float %28
+%33 = OpFMul %v4float %32 %6
+OpStore %28 %33
+%34 = OpAccessChain %_ptr_Function_float %28 %int_0
+%35 = OpLoad %float %34
+OpStore %29 %35
+%17 = OpLoad %float %29
+OpReturnValue %17
+OpFunctionEnd
+%fn = OpFunction %float None %11
+%color_0 = OpFunctionParameter %_ptr_Function_v4float
+%bb_entry_0 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %color_0
+OpStore %v %22
+%23 = OpLoad %v4float %v
+%24 = OpFMul %v4float %23 %6
+OpStore %v %24
+%26 = OpAccessChain %_ptr_Function_float %v %int_0
+%27 = OpLoad %float %26
+OpReturnValue %27
+OpFunctionEnd
+)";
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<InlineExhaustivePass>(before, after, false, true);
+}
+
 TEST_F(InlineTest, InlineFuncWithOpTerminateRayNotInContinue) {
   const std::string text =
       R"(
@@ -3259,6 +3383,82 @@
   SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
 }
 
+TEST_F(InlineTest, ShaderDebugSimple) {
+  // Same as DebugSimple but for NonSemantic.Shader.DebugInfo.100.
+  const std::string text = R"(
+; CHECK: [[main_name:%\d+]] = OpString "main"
+; CHECK: [[foo_name:%\d+]] = OpString "foo"
+; CHECK: [[dbg_main:%\d+]] = OpExtInst %void {{%\d+}} DebugFunction [[main_name]] {{%\d+}} {{%\d+}} %uint_4 %uint_1 {{%\d+}} [[main_name]] %uint_3 %uint_4
+; CHECK: [[dbg_foo:%\d+]] = OpExtInst %void {{%\d+}} DebugFunction [[foo_name]] {{%\d+}} {{%\d+}} %uint_1 %uint_1 {{%\d+}} [[foo_name]] %uint_3 %uint_1
+; CHECK: [[foo_bb:%\d+]] = OpExtInst %void {{%\d+}} DebugLexicalBlock {{%\d+}} %uint_1 %uint_14 [[dbg_foo]]
+; CHECK: [[inlined_at:%\d+]] = OpExtInst %void {{%\d+}} DebugInlinedAt %uint_4 [[dbg_main]]
+; CHECK: [[main:%\d+]] = OpFunction %void None
+; CHECK: {{%\d+}} = OpExtInst %void {{%\d+}} DebugScope [[foo_bb]] [[inlined_at]]
+; CHECK: [[foo:%\d+]] = OpFunction %v4float None
+               OpCapability Shader
+               OpExtension "SPV_KHR_non_semantic_info"
+          %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %3 %4
+               OpExecutionMode %main OriginUpperLeft
+          %5 = OpString "ps.hlsl"
+               OpSource HLSL 600 %5
+          %6 = OpString "float"
+  %main_name = OpString "main"
+   %foo_name = OpString "foo"
+               OpDecorate %3 Location 0
+               OpDecorate %4 Location 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+     %uint_4 = OpConstant %uint 4
+     %uint_5 = OpConstant %uint 5
+    %uint_14 = OpConstant %uint 14
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+    %float_1 = OpConstant %float 1
+    %v4float = OpTypeVector %float 4
+         %14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+         %19 = OpTypeFunction %v4float
+          %3 = OpVariable %_ptr_Input_v4float Input
+          %4 = OpVariable %_ptr_Output_v4float Output
+         %20 = OpExtInst %void %1 DebugSource %5
+         %21 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %20 %uint_5
+         %22 = OpExtInst %void %1 DebugTypeBasic %6 %uint_32 %uint_3 %uint_0
+         %23 = OpExtInst %void %1 DebugTypeVector %22 %uint_4
+         %24 = OpExtInst %void %1 DebugTypeFunction %uint_3 %23 %23
+         %25 = OpExtInst %void %1 DebugTypeFunction %uint_3 %23
+   %dbg_main = OpExtInst %void %1 DebugFunction %main_name %24 %20 %uint_4 %uint_1 %21 %main_name %uint_3 %uint_4
+    %dbg_foo = OpExtInst %void %1 DebugFunction %foo_name %25 %20 %uint_1 %uint_1 %21 %foo_name %uint_3 %uint_1
+         %29 = OpExtInst %void %1 DebugLexicalBlock %20 %uint_1 %uint_14 %dbg_foo
+       %main = OpFunction %void None %18
+         %30 = OpLabel
+%dbg_main_def = OpExtInst %void %1 DebugFunctionDefinition %dbg_main %main
+         %31 = OpExtInst %void %1 DebugScope %dbg_main
+         %32 = OpFunctionCall %v4float %foo
+         %33 = OpLoad %v4float %3
+         %34 = OpFAdd %v4float %32 %33
+               OpStore %4 %34
+               OpReturn
+               OpFunctionEnd
+        %foo = OpFunction %v4float None %19
+         %36 = OpLabel
+%dbg_foo_def = OpExtInst %void %1 DebugFunctionDefinition %dbg_foo %foo
+         %35 = OpExtInst %void %1 DebugScope %dbg_foo
+         %37 = OpExtInst %void %1 DebugScope %29
+               OpReturnValue %14
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
 TEST_F(InlineTest, DebugNested) {
   // When function main() calls function zoo() and function zoo() calls
   // function bar() and function bar() calls function foo(), check that
@@ -3462,6 +3662,103 @@
   SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
 }
 
+TEST_F(InlineTest, ShaderDebugSimpleHLSLPixelShader) {
+  // Same as DebugSimpleHLSLPixelShader but for
+  // NonSemantic.Shader.DebugInfo.100.
+  const std::string text = R"(
+; CHECK: [[dbg_main:%\d+]] = OpExtInst %void [[ext:%\d+]] DebugFunction {{%\d+}} {{%\d+}} {{%\d+}} %uint_1 %uint_1 {{%\d+}} {{%\d+}} %uint_3 %uint_1
+; CHECK: [[lex_blk:%\d+]] = OpExtInst %void [[ext]] DebugLexicalBlock {{%\d+}} %uint_1 %uint_47 [[dbg_main]]
+; CHECK: %main = OpFunction %void None
+; CHECK: {{%\d+}} = OpExtInst %void [[ext]] DebugScope [[dbg_main]]
+; CHECK: {{%\d+}} = OpExtInst %void [[ext]] DebugDeclare {{%\d+}} %param_var_color
+; CHECK: {{%\d+}} = OpExtInst %void [[ext]] DebugScope [[lex_blk]]
+; CHECK: {{%\d+}} = OpExtInst %void %1 DebugLine {{%\d+}} %uint_2 %uint_2 %uint_10 %uint_10
+; CHECK: {{%\d+}} = OpLoad %v4float %param_var_color
+; CHECK: {{%\d+}} = OpExtInst %void %1 DebugLine {{%\d+}} %uint_2 %uint_2 %uint_3 %uint_3
+; CHECK: OpFunctionEnd
+; CHECK: %src_main = OpFunction %v4float None
+               OpCapability Shader
+               OpExtension "SPV_KHR_non_semantic_info"
+          %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %in_var_COLOR %out_var_SV_TARGET
+               OpExecutionMode %main OriginUpperLeft
+          %5 = OpString "ps.hlsl"
+               OpSource HLSL 600 %5
+         %14 = OpString "#line 1 \"ps.hlsl\"
+float4 main(float4 color : COLOR) : SV_TARGET {
+  return color;
+}
+"
+         %17 = OpString "float"
+         %21 = OpString "src.main"
+         %24 = OpString "color"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %main "main"
+               OpName %param_var_color "param.var.color"
+               OpName %src_main "src.main"
+               OpName %color "color"
+               OpName %bb_entry "bb.entry"
+               OpDecorate %in_var_COLOR Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+     %uint_3 = OpConstant %uint 3
+     %uint_4 = OpConstant %uint 4
+     %uint_5 = OpConstant %uint 5
+    %uint_10 = OpConstant %uint 10
+    %uint_20 = OpConstant %uint 20
+    %uint_32 = OpConstant %uint 32
+    %uint_47 = OpConstant %uint 47
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %27 = OpTypeFunction %void
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+         %33 = OpTypeFunction %v4float %_ptr_Function_v4float
+%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+         %13 = OpExtInst %void %1 DebugExpression
+         %15 = OpExtInst %void %1 DebugSource %5 %14
+         %16 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %15 %uint_5
+         %18 = OpExtInst %void %1 DebugTypeBasic %17 %uint_32 %uint_3 %uint_0
+         %19 = OpExtInst %void %1 DebugTypeVector %18 %uint_4
+         %20 = OpExtInst %void %1 DebugTypeFunction %uint_3 %19 %19
+         %22 = OpExtInst %void %1 DebugFunction %21 %20 %15 %uint_1 %uint_1 %16 %21 %uint_3 %uint_1
+         %25 = OpExtInst %void %1 DebugLocalVariable %24 %19 %15 %uint_1 %uint_20 %22 %uint_4 %uint_0
+         %26 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_1 %uint_47 %22
+       %main = OpFunction %void None %27
+         %28 = OpLabel
+%param_var_color = OpVariable %_ptr_Function_v4float Function
+         %31 = OpLoad %v4float %in_var_COLOR
+               OpStore %param_var_color %31
+         %32 = OpFunctionCall %v4float %src_main %param_var_color
+               OpStore %out_var_SV_TARGET %32
+               OpReturn
+               OpFunctionEnd
+   %src_main = OpFunction %v4float None %33
+      %color = OpFunctionParameter %_ptr_Function_v4float
+   %bb_entry = OpLabel
+        %140 = OpExtInst %void %1 DebugFunctionDefinition %22 %src_main
+        %141 = OpExtInst %void %1 DebugLine %5 %uint_1 %uint_1 %uint_1 %uint_1
+         %34 = OpExtInst %void %1 DebugScope %22
+         %36 = OpExtInst %void %1 DebugDeclare %25 %color %13
+         %38 = OpExtInst %void %1 DebugScope %26
+        %142 = OpExtInst %void %1 DebugLine %5 %uint_2 %uint_2 %uint_10 %uint_10
+         %39 = OpLoad %v4float %color
+        %143 = OpExtInst %void %1 DebugLine %5 %uint_2 %uint_2 %uint_3 %uint_3
+               OpReturnValue %39
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
 TEST_F(InlineTest, DebugDeclareForCalleeFunctionParam) {
   // Check that InlinePass correctly generates DebugDeclare instructions
   // for callee function's parameters and maps them to corresponding
@@ -3937,6 +4234,105 @@
   SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
 }
 
+TEST_F(InlineTest, CreateConstantForInlinedAt) {
+  // This shader causes CreateDebugInlinedAt to generate a constant.
+  // Using the Constant manager would attempt to build the invalidated
+  // DefUse manager during inlining which could cause an assert because
+  // the function is in an inconsistant state. This test verifies that
+  // CreateDebugInlinedAt detects that the DefUse manager is disabled
+  // and creates a duplicate constant safely without the Constant manager.
+  //
+  // int function1() {
+  //   return 1;
+  // }
+  //
+  // void main() {
+  //   function1();
+  // }
+
+  const std::string text = R"(OpCapability Shader
+; CHECK: %uint_7 = OpConstant %uint 7
+; CHECK: %uint_7_0 = OpConstant %uint 7
+; CHECK: OpExtInst %void %1 DebugInlinedAt %uint_7_0
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%3 = OpString "parent3.hlsl"
+%8 = OpString "int"
+%19 = OpString "function1"
+%20 = OpString ""
+%26 = OpString "main"
+OpName %main "main"
+OpName %src_main "src.main"
+OpName %bb_entry "bb.entry"
+OpName %function1 "function1"
+OpName %bb_entry_0 "bb.entry"
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%void = OpTypeVoid
+%uint_4 = OpConstant %uint 4
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_1 = OpConstant %uint 1
+%uint_5 = OpConstant %uint 5
+%uint_2 = OpConstant %uint 2
+%uint_17 = OpConstant %uint 17
+%uint_6 = OpConstant %uint 6
+%uint_13 = OpConstant %uint 13
+%uint_7 = OpConstant %uint 7
+%31 = OpTypeFunction %void
+%42 = OpTypeFunction %int
+%10 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 %uint_4 %uint_0
+%13 = OpExtInst %void %1 DebugTypeFunction %uint_3 %10
+%15 = OpExtInst %void %1 DebugSource %3
+%16 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %15 %uint_5
+%21 = OpExtInst %void %1 DebugFunction %19 %13 %15 %uint_2 %uint_1 %16 %20 %uint_3 %uint_2
+%23 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_2 %uint_17 %21
+%25 = OpExtInst %void %1 DebugTypeFunction %uint_3 %void
+%27 = OpExtInst %void %1 DebugFunction %26 %25 %15 %uint_6 %uint_1 %16 %20 %uint_3 %uint_6
+%29 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_6 %uint_13 %27
+%main = OpFunction %void None %31
+%32 = OpLabel
+%33 = OpFunctionCall %void %src_main
+OpLine %3 8 1
+OpReturn
+OpFunctionEnd
+OpLine %3 6 1
+%src_main = OpFunction %void None %31
+OpNoLine
+%bb_entry = OpLabel
+%47 = OpExtInst %void %1 DebugScope %27
+%37 = OpExtInst %void %1 DebugFunctionDefinition %27 %src_main
+%48 = OpExtInst %void %1 DebugScope %29
+OpLine %3 7 3
+%39 = OpFunctionCall %int %function1
+%49 = OpExtInst %void %1 DebugScope %27
+OpLine %3 8 1
+OpReturn
+%50 = OpExtInst %void %1 DebugNoScope
+OpFunctionEnd
+OpLine %3 2 1
+%function1 = OpFunction %int None %42
+OpNoLine
+%bb_entry_0 = OpLabel
+%51 = OpExtInst %void %1 DebugScope %21
+%45 = OpExtInst %void %1 DebugFunctionDefinition %21 %function1
+%52 = OpExtInst %void %1 DebugScope %23
+OpLine %3 3 3
+OpReturnValue %int_1
+%53 = OpExtInst %void %1 DebugNoScope
+OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Empty modules
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
index f189962..1a42329 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -7308,15 +7308,15 @@
  ;CHECK: %_ptr_StorageBuffer__struct_128 = OpTypePointer StorageBuffer %_struct_128
  ;CHECK:         %130 = OpVariable %_ptr_StorageBuffer__struct_128 StorageBuffer
  ;CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
- ;CHECK:      %uint_3 = OpConstant %uint 3
+ ;CHECK:     %uint_4 = OpConstant %uint 4
  ;CHECK:         %148 = OpTypeFunction %void %uint %uint %uint %uint %uint
  ;CHECK: %_struct_155 = OpTypeStruct %uint %_runtimearr_uint
  ;CHECK: %_ptr_StorageBuffer__struct_155 = OpTypePointer StorageBuffer %_struct_155
  ;CHECK:        %157 = OpVariable %_ptr_StorageBuffer__struct_155 StorageBuffer
  ;CHECK:    %uint_11 = OpConstant %uint 11
- ;CHECK:      %uint_4 = OpConstant %uint 4
  ;CHECK:    %uint_23 = OpConstant %uint 23
  ;CHECK:     %uint_2 = OpConstant %uint 2
+ ;CHECK:      %uint_3 = OpConstant %uint 3
  ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
  ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
  ;CHECK:     %v4uint = OpTypeVector %uint 4
@@ -7352,7 +7352,7 @@
  ;CHECK:        %146 = OpLoad %v2float %86
  ;CHECK:               OpBranch %143
  ;CHECK:        %145 = OpLabel
- ;CHECK:        %201 = OpFunctionCall %void %147 %uint_71 %uint_3 %uint_0 %119 %140
+ ;CHECK:        %201 = OpFunctionCall %void %147 %uint_71 %uint_4 %uint_0 %119 %140
  ;CHECK:               OpBranch %143
  ;CHECK:        %143 = OpLabel
  ;CHECK:        %203 = OpPhi %v2float %146 %144 %202 %145
@@ -7369,7 +7369,7 @@
  ;CHECK:        %209 = OpLoad %v2float %89
  ;CHECK:               OpBranch %206
  ;CHECK:        %208 = OpLabel
- ;CHECK:        %211 = OpFunctionCall %void %147 %uint_75 %uint_3 %uint_0 %204 %140
+ ;CHECK:        %211 = OpFunctionCall %void %147 %uint_75 %uint_4 %uint_0 %204 %140
  ;CHECK:               OpBranch %206
  ;CHECK:        %206 = OpLabel
  ;CHECK:        %212 = OpPhi %v2float %209 %207 %202 %208
@@ -7409,49 +7409,49 @@
  ;CHECK:        %153 = OpFunctionParameter %uint
  ;CHECK:        %154 = OpLabel
  ;CHECK:        %158 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_0
- ;CHECK:        %161 = OpAtomicIAdd %uint %158 %uint_4 %uint_0 %uint_11
- ;CHECK:        %162 = OpIAdd %uint %161 %uint_11
- ;CHECK:        %163 = OpArrayLength %uint %157 1
- ;CHECK:        %164 = OpULessThanEqual %bool %162 %163
- ;CHECK:               OpSelectionMerge %165 None
- ;CHECK:               OpBranchConditional %164 %166 %165
- ;CHECK:        %166 = OpLabel
- ;CHECK:        %167 = OpIAdd %uint %161 %uint_0
- ;CHECK:        %168 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %167
- ;CHECK:               OpStore %168 %uint_11
- ;CHECK:        %170 = OpIAdd %uint %161 %uint_1
- ;CHECK:        %171 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %170
- ;CHECK:               OpStore %171 %uint_23
- ;CHECK:        %173 = OpIAdd %uint %161 %uint_2
- ;CHECK:        %174 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %173
- ;CHECK:               OpStore %174 %149
- ;CHECK:        %175 = OpIAdd %uint %161 %uint_3
+ ;CHECK:        %160 = OpAtomicIAdd %uint %158 %uint_4 %uint_0 %uint_11
+ ;CHECK:        %161 = OpIAdd %uint %160 %uint_11
+ ;CHECK:        %162 = OpArrayLength %uint %157 1
+ ;CHECK:        %163 = OpULessThanEqual %bool %161 %162
+ ;CHECK:               OpSelectionMerge %164 None
+ ;CHECK:               OpBranchConditional %163 %165 %164
+ ;CHECK:        %165 = OpLabel
+ ;CHECK:        %166 = OpIAdd %uint %160 %uint_0
+ ;CHECK:        %167 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %166
+ ;CHECK:               OpStore %167 %uint_11
+ ;CHECK:        %169 = OpIAdd %uint %160 %uint_1
+ ;CHECK:        %170 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %169
+ ;CHECK:               OpStore %170 %uint_23
+ ;CHECK:        %172 = OpIAdd %uint %160 %uint_2
+ ;CHECK:        %173 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %172
+ ;CHECK:               OpStore %173 %149
+ ;CHECK:        %175 = OpIAdd %uint %160 %uint_3
  ;CHECK:        %176 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %175
  ;CHECK:               OpStore %176 %uint_4
  ;CHECK:        %179 = OpLoad %v4float %gl_FragCoord
  ;CHECK:        %181 = OpBitcast %v4uint %179
  ;CHECK:        %182 = OpCompositeExtract %uint %181 0
- ;CHECK:        %183 = OpIAdd %uint %161 %uint_4
+ ;CHECK:        %183 = OpIAdd %uint %160 %uint_4
  ;CHECK:        %184 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %183
  ;CHECK:               OpStore %184 %182
  ;CHECK:        %185 = OpCompositeExtract %uint %181 1
- ;CHECK:        %187 = OpIAdd %uint %161 %uint_5
+ ;CHECK:        %187 = OpIAdd %uint %160 %uint_5
  ;CHECK:        %188 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %187
  ;CHECK:               OpStore %188 %185
- ;CHECK:        %189 = OpIAdd %uint %161 %uint_7
+ ;CHECK:        %189 = OpIAdd %uint %160 %uint_7
  ;CHECK:        %190 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %189
  ;CHECK:               OpStore %190 %150
- ;CHECK:        %192 = OpIAdd %uint %161 %uint_8
+ ;CHECK:        %192 = OpIAdd %uint %160 %uint_8
  ;CHECK:        %193 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %192
  ;CHECK:               OpStore %193 %151
- ;CHECK:        %195 = OpIAdd %uint %161 %uint_9
+ ;CHECK:        %195 = OpIAdd %uint %160 %uint_9
  ;CHECK:        %196 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %195
  ;CHECK:               OpStore %196 %152
- ;CHECK:        %198 = OpIAdd %uint %161 %uint_10
+ ;CHECK:        %198 = OpIAdd %uint %160 %uint_10
  ;CHECK:        %199 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %198
  ;CHECK:               OpStore %199 %153
- ;CHECK:               OpBranch %165
- ;CHECK:        %165 = OpLabel
+ ;CHECK:               OpBranch %164
+ ;CHECK:        %164 = OpLabel
  ;CHECK:               OpReturn
  ;CHECK:               OpFunctionEnd
  )";
@@ -7596,14 +7596,14 @@
 ;CHECK:        %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
-;CHECK:     %uint_3 = OpConstant %uint 3
+;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:        %132 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:%_struct_139 = OpTypeStruct %uint %_runtimearr_uint
 ;CHECK:%_ptr_StorageBuffer__struct_139 = OpTypePointer StorageBuffer %_struct_139
 ;CHECK:        %141 = OpVariable %_ptr_StorageBuffer__struct_139 StorageBuffer
 ;CHECK:    %uint_11 = OpConstant %uint 11
-;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:    %uint_23 = OpConstant %uint 23
+;CHECK:     %uint_3 = OpConstant %uint 3
 ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:     %v4uint = OpTypeVector %uint 4
@@ -7637,7 +7637,7 @@
 ;CHECK:        %130 = OpLoad %v2float %81
 ;CHECK:               OpBranch %127
 ;CHECK:        %129 = OpLabel
-;CHECK:        %184 = OpFunctionCall %void %131 %uint_78 %uint_3 %uint_0 %101 %123
+;CHECK:        %184 = OpFunctionCall %void %131 %uint_78 %uint_4 %uint_0 %101 %123
 ;CHECK:               OpBranch %127
 ;CHECK:        %127 = OpLabel
 ;CHECK:        %186 = OpPhi %v2float %130 %128 %185 %129
@@ -7674,49 +7674,49 @@
 ;CHECK:        %137 = OpFunctionParameter %uint
 ;CHECK:        %138 = OpLabel
 ;CHECK:        %142 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_0
-;CHECK:        %145 = OpAtomicIAdd %uint %142 %uint_4 %uint_0 %uint_11
-;CHECK:        %146 = OpIAdd %uint %145 %uint_11
-;CHECK:        %147 = OpArrayLength %uint %141 1
-;CHECK:        %148 = OpULessThanEqual %bool %146 %147
-;CHECK:               OpSelectionMerge %149 None
-;CHECK:               OpBranchConditional %148 %150 %149
-;CHECK:        %150 = OpLabel
-;CHECK:        %151 = OpIAdd %uint %145 %uint_0
-;CHECK:        %152 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %151
-;CHECK:               OpStore %152 %uint_11
-;CHECK:        %154 = OpIAdd %uint %145 %uint_1
-;CHECK:        %155 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %154
-;CHECK:               OpStore %155 %uint_23
-;CHECK:        %156 = OpIAdd %uint %145 %uint_2
-;CHECK:        %157 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %156
-;CHECK:               OpStore %157 %133
-;CHECK:        %158 = OpIAdd %uint %145 %uint_3
+;CHECK:        %144 = OpAtomicIAdd %uint %142 %uint_4 %uint_0 %uint_11
+;CHECK:        %145 = OpIAdd %uint %144 %uint_11
+;CHECK:        %146 = OpArrayLength %uint %141 1
+;CHECK:        %147 = OpULessThanEqual %bool %145 %146
+;CHECK:               OpSelectionMerge %148 None
+;CHECK:               OpBranchConditional %147 %149 %148
+;CHECK:        %149 = OpLabel
+;CHECK:        %150 = OpIAdd %uint %144 %uint_0
+;CHECK:        %151 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %150
+;CHECK:               OpStore %151 %uint_11
+;CHECK:        %153 = OpIAdd %uint %144 %uint_1
+;CHECK:        %154 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %153
+;CHECK:               OpStore %154 %uint_23
+;CHECK:        %155 = OpIAdd %uint %144 %uint_2
+;CHECK:        %156 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %155
+;CHECK:               OpStore %156 %133
+;CHECK:        %158 = OpIAdd %uint %144 %uint_3
 ;CHECK:        %159 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %158
 ;CHECK:               OpStore %159 %uint_4
 ;CHECK:        %162 = OpLoad %v4float %gl_FragCoord
 ;CHECK:        %164 = OpBitcast %v4uint %162
 ;CHECK:        %165 = OpCompositeExtract %uint %164 0
-;CHECK:        %166 = OpIAdd %uint %145 %uint_4
+;CHECK:        %166 = OpIAdd %uint %144 %uint_4
 ;CHECK:        %167 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %166
 ;CHECK:               OpStore %167 %165
 ;CHECK:        %168 = OpCompositeExtract %uint %164 1
-;CHECK:        %170 = OpIAdd %uint %145 %uint_5
+;CHECK:        %170 = OpIAdd %uint %144 %uint_5
 ;CHECK:        %171 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %170
 ;CHECK:               OpStore %171 %168
-;CHECK:        %172 = OpIAdd %uint %145 %uint_7
+;CHECK:        %172 = OpIAdd %uint %144 %uint_7
 ;CHECK:        %173 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %172
 ;CHECK:               OpStore %173 %134
-;CHECK:        %175 = OpIAdd %uint %145 %uint_8
+;CHECK:        %175 = OpIAdd %uint %144 %uint_8
 ;CHECK:        %176 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %175
 ;CHECK:               OpStore %176 %135
-;CHECK:        %178 = OpIAdd %uint %145 %uint_9
+;CHECK:        %178 = OpIAdd %uint %144 %uint_9
 ;CHECK:        %179 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %178
 ;CHECK:               OpStore %179 %136
-;CHECK:        %181 = OpIAdd %uint %145 %uint_10
+;CHECK:        %181 = OpIAdd %uint %144 %uint_10
 ;CHECK:        %182 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %181
 ;CHECK:               OpStore %182 %137
-;CHECK:               OpBranch %149
-;CHECK:        %149 = OpLabel
+;CHECK:               OpBranch %148
+;CHECK:        %148 = OpLabel
 ;CHECK:               OpReturn
 ;CHECK:               OpFunctionEnd
  )";
@@ -7743,11 +7743,11 @@
                OpExecutionMode %MainPs OriginUpperLeft
                OpSource HLSL 500
                OpName %MainPs "MainPs"
-               OpName %PerBatchEnvMapConstantBuffer_t
-"PerBatchEnvMapConstantBuffer_t" OpMemberName %PerBatchEnvMapConstantBuffer_t 0
-"g_matEnvMapWorldToLocal" OpMemberName %PerBatchEnvMapConstantBuffer_t 1
-"g_vEnvironmentMapBoxMins" OpMemberName %PerBatchEnvMapConstantBuffer_t 2
-"g_TexOff" OpName %_BindlessFastEnvMapCB_PS_t "_BindlessFastEnvMapCB_PS_t"
+               OpName %PerBatchEnvMapConstantBuffer_t "PerBatchEnvMapConstantBuffer_t"
+               OpMemberName %PerBatchEnvMapConstantBuffer_t 0 "g_matEnvMapWorldToLocal"
+               OpMemberName %PerBatchEnvMapConstantBuffer_t 1 "g_vEnvironmentMapBoxMins"
+               OpMemberName %PerBatchEnvMapConstantBuffer_t 2 "g_TexOff"
+               OpName %_BindlessFastEnvMapCB_PS_t "_BindlessFastEnvMapCB_PS_t"
                OpMemberName %_BindlessFastEnvMapCB_PS_t 0 "g_envMapConstants"
                OpName %_ ""
                OpName %PerViewPushConst_t "PerViewPushConst_t"
@@ -7831,15 +7831,15 @@
 ;CHECK:        %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
-;CHECK:     %uint_3 = OpConstant %uint 3
+;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:        %135 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:%_struct_142 = OpTypeStruct %uint %_runtimearr_uint
 ;CHECK:%_ptr_StorageBuffer__struct_142 = OpTypePointer StorageBuffer %_struct_142
 ;CHECK:        %144 = OpVariable %_ptr_StorageBuffer__struct_142 StorageBuffer
 ;CHECK:    %uint_11 = OpConstant %uint 11
-;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:     %uint_1 = OpConstant %uint 1
 ;CHECK:    %uint_23 = OpConstant %uint 23
+;CHECK:     %uint_3 = OpConstant %uint 3
 ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:     %v4uint = OpTypeVector %uint 4
@@ -7878,13 +7878,15 @@
 ;CHECK:        %133 = OpLoad %v2float %81
 ;CHECK:               OpBranch %130
 ;CHECK:        %132 = OpLabel
-;CHECK:        %188 = OpFunctionCall %void %134 %uint_78 %uint_3 %uint_0 %101 %126
+;CHECK:        %188 = OpFunctionCall %void %134 %uint_78 %uint_4 %uint_0 %101 %126
 ;CHECK:               OpBranch %130
 ;CHECK:        %130 = OpLabel
 ;CHECK:        %190 = OpPhi %v2float %133 %131 %189 %132
 ;CHECK:         %86 = OpFAdd %v2float %66 %190
-         %87 = OpLoad %46 %g_tColor %88 = OpLoad %50 %g_sAniso %89 =
-               OpSampledImage %54 %87 %88 %91 = OpImageSampleImplicitLod %v4float %89 %86
+         %87 = OpLoad %46 %g_tColor
+         %88 = OpLoad %50 %g_sAniso
+         %89 = OpSampledImage %54 %87 %88
+         %91 = OpImageSampleImplicitLod %v4float %89 %86
                OpStore %_entryPointOutput_vColor %91
 ;CHECK-NOT: %91 = OpImageSampleImplicitLod %v4float %89 %86
 ;CHECK-NOT:       OpStore %_entryPointOutput_vColor %91
@@ -7931,49 +7933,49 @@
 ;CHECK:        %140 = OpFunctionParameter %uint
 ;CHECK:        %141 = OpLabel
 ;CHECK:        %145 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_0
-;CHECK:        %148 = OpAtomicIAdd %uint %145 %uint_4 %uint_0 %uint_11
-;CHECK:        %149 = OpIAdd %uint %148 %uint_11
-;CHECK:        %150 = OpArrayLength %uint %144 1
-;CHECK:        %151 = OpULessThanEqual %bool %149 %150
-;CHECK:               OpSelectionMerge %152 None
-;CHECK:               OpBranchConditional %151 %153 %152
-;CHECK:        %153 = OpLabel
-;CHECK:        %154 = OpIAdd %uint %148 %uint_0
-;CHECK:        %156 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %154
-;CHECK:               OpStore %156 %uint_11
-;CHECK:        %158 = OpIAdd %uint %148 %uint_1
-;CHECK:        %159 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %158
-;CHECK:               OpStore %159 %uint_23
-;CHECK:        %160 = OpIAdd %uint %148 %uint_2
-;CHECK:        %161 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %160
-;CHECK:               OpStore %161 %136
-;CHECK:        %162 = OpIAdd %uint %148 %uint_3
+;CHECK:        %147 = OpAtomicIAdd %uint %145 %uint_4 %uint_0 %uint_11
+;CHECK:        %148 = OpIAdd %uint %147 %uint_11
+;CHECK:        %149 = OpArrayLength %uint %144 1
+;CHECK:        %150 = OpULessThanEqual %bool %148 %149
+;CHECK:               OpSelectionMerge %151 None
+;CHECK:               OpBranchConditional %150 %152 %151
+;CHECK:        %152 = OpLabel
+;CHECK:        %153 = OpIAdd %uint %147 %uint_0
+;CHECK:        %155 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %153
+;CHECK:               OpStore %155 %uint_11
+;CHECK:        %157 = OpIAdd %uint %147 %uint_1
+;CHECK:        %158 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %157
+;CHECK:               OpStore %158 %uint_23
+;CHECK:        %159 = OpIAdd %uint %147 %uint_2
+;CHECK:        %160 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %159
+;CHECK:               OpStore %160 %136
+;CHECK:        %162 = OpIAdd %uint %147 %uint_3
 ;CHECK:        %163 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %162
 ;CHECK:               OpStore %163 %uint_4
 ;CHECK:        %166 = OpLoad %v4float %gl_FragCoord
 ;CHECK:        %168 = OpBitcast %v4uint %166
 ;CHECK:        %169 = OpCompositeExtract %uint %168 0
-;CHECK:        %170 = OpIAdd %uint %148 %uint_4
+;CHECK:        %170 = OpIAdd %uint %147 %uint_4
 ;CHECK:        %171 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %170
 ;CHECK:               OpStore %171 %169
 ;CHECK:        %172 = OpCompositeExtract %uint %168 1
-;CHECK:        %174 = OpIAdd %uint %148 %uint_5
+;CHECK:        %174 = OpIAdd %uint %147 %uint_5
 ;CHECK:        %175 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %174
 ;CHECK:               OpStore %175 %172
-;CHECK:        %176 = OpIAdd %uint %148 %uint_7
+;CHECK:        %176 = OpIAdd %uint %147 %uint_7
 ;CHECK:        %177 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %176
 ;CHECK:               OpStore %177 %137
-;CHECK:        %179 = OpIAdd %uint %148 %uint_8
+;CHECK:        %179 = OpIAdd %uint %147 %uint_8
 ;CHECK:        %180 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %179
 ;CHECK:               OpStore %180 %138
-;CHECK:        %182 = OpIAdd %uint %148 %uint_9
+;CHECK:        %182 = OpIAdd %uint %147 %uint_9
 ;CHECK:        %183 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %182
 ;CHECK:               OpStore %183 %139
-;CHECK:        %185 = OpIAdd %uint %148 %uint_10
+;CHECK:        %185 = OpIAdd %uint %147 %uint_10
 ;CHECK:        %186 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %185
 ;CHECK:               OpStore %186 %140
-;CHECK:               OpBranch %152
-;CHECK:        %152 = OpLabel
+;CHECK:               OpBranch %151
+;CHECK:        %151 = OpLabel
 ;CHECK:               OpReturn
 ;CHECK:               OpFunctionEnd
  )";
@@ -8344,15 +8346,15 @@
 ;CHECK:         %69 = OpVariable %_ptr_StorageBuffer__struct_67 StorageBuffer
 ;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
 ;CHECK:       %bool = OpTypeBool
-;CHECK:     %uint_3 = OpConstant %uint 3
+;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:         %88 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK: %_struct_95 = OpTypeStruct %uint %_runtimearr_uint
 ;CHECK:%_ptr_StorageBuffer__struct_95 = OpTypePointer StorageBuffer %_struct_95
 ;CHECK:         %97 = OpVariable %_ptr_StorageBuffer__struct_95 StorageBuffer
 ;CHECK:    %uint_11 = OpConstant %uint 11
-;CHECK:     %uint_4 = OpConstant %uint 4
 ;CHECK:    %uint_23 = OpConstant %uint 23
 ;CHECK:     %uint_2 = OpConstant %uint 2
+;CHECK:     %uint_3 = OpConstant %uint 3
 ;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:     %v4uint = OpTypeVector %uint 4
@@ -8389,7 +8391,7 @@
 ;CHECK:         %86 = OpLoad %v2float %41
 ;CHECK:               OpBranch %83
 ;CHECK:         %85 = OpLabel
-;CHECK:        %141 = OpFunctionCall %void %87 %uint_81 %uint_3 %uint_0 %58 %79
+;CHECK:        %141 = OpFunctionCall %void %87 %uint_81 %uint_4 %uint_0 %58 %79
 ;CHECK:               OpBranch %83
 ;CHECK:         %83 = OpLabel
 ;CHECK:        %143 = OpPhi %v2float %86 %84 %142 %85
@@ -8424,49 +8426,49 @@
 ;CHECK:         %93 = OpFunctionParameter %uint
 ;CHECK:         %94 = OpLabel
 ;CHECK:         %98 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_0
-;CHECK:        %101 = OpAtomicIAdd %uint %98 %uint_4 %uint_0 %uint_11
-;CHECK:        %102 = OpIAdd %uint %101 %uint_11
-;CHECK:        %103 = OpArrayLength %uint %97 1
-;CHECK:        %104 = OpULessThanEqual %bool %102 %103
-;CHECK:               OpSelectionMerge %105 None
-;CHECK:               OpBranchConditional %104 %106 %105
-;CHECK:        %106 = OpLabel
-;CHECK:        %107 = OpIAdd %uint %101 %uint_0
-;CHECK:        %108 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %107
-;CHECK:               OpStore %108 %uint_11
-;CHECK:        %110 = OpIAdd %uint %101 %uint_1
-;CHECK:        %111 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %110
-;CHECK:               OpStore %111 %uint_23
-;CHECK:        %113 = OpIAdd %uint %101 %uint_2
-;CHECK:        %114 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %113
-;CHECK:               OpStore %114 %89
-;CHECK:        %115 = OpIAdd %uint %101 %uint_3
+;CHECK:        %100 = OpAtomicIAdd %uint %98 %uint_4 %uint_0 %uint_11
+;CHECK:        %101 = OpIAdd %uint %100 %uint_11
+;CHECK:        %102 = OpArrayLength %uint %97 1
+;CHECK:        %103 = OpULessThanEqual %bool %101 %102
+;CHECK:               OpSelectionMerge %104 None
+;CHECK:               OpBranchConditional %103 %105 %104
+;CHECK:        %105 = OpLabel
+;CHECK:        %106 = OpIAdd %uint %100 %uint_0
+;CHECK:        %107 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %106
+;CHECK:               OpStore %107 %uint_11
+;CHECK:        %109 = OpIAdd %uint %100 %uint_1
+;CHECK:        %110 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %109
+;CHECK:               OpStore %110 %uint_23
+;CHECK:        %112 = OpIAdd %uint %100 %uint_2
+;CHECK:        %113 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %112
+;CHECK:               OpStore %113 %89
+;CHECK:        %115 = OpIAdd %uint %100 %uint_3
 ;CHECK:        %116 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %115
 ;CHECK:               OpStore %116 %uint_4
 ;CHECK:        %119 = OpLoad %v4float %gl_FragCoord
 ;CHECK:        %121 = OpBitcast %v4uint %119
 ;CHECK:        %122 = OpCompositeExtract %uint %121 0
-;CHECK:        %123 = OpIAdd %uint %101 %uint_4
+;CHECK:        %123 = OpIAdd %uint %100 %uint_4
 ;CHECK:        %124 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %123
 ;CHECK:               OpStore %124 %122
 ;CHECK:        %125 = OpCompositeExtract %uint %121 1
-;CHECK:        %127 = OpIAdd %uint %101 %uint_5
+;CHECK:        %127 = OpIAdd %uint %100 %uint_5
 ;CHECK:        %128 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %127
 ;CHECK:               OpStore %128 %125
-;CHECK:        %129 = OpIAdd %uint %101 %uint_7
+;CHECK:        %129 = OpIAdd %uint %100 %uint_7
 ;CHECK:        %130 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %129
 ;CHECK:               OpStore %130 %90
-;CHECK:        %132 = OpIAdd %uint %101 %uint_8
+;CHECK:        %132 = OpIAdd %uint %100 %uint_8
 ;CHECK:        %133 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %132
 ;CHECK:               OpStore %133 %91
-;CHECK:        %135 = OpIAdd %uint %101 %uint_9
+;CHECK:        %135 = OpIAdd %uint %100 %uint_9
 ;CHECK:        %136 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %135
 ;CHECK:               OpStore %136 %92
-;CHECK:        %138 = OpIAdd %uint %101 %uint_10
+;CHECK:        %138 = OpIAdd %uint %100 %uint_10
 ;CHECK:        %139 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %138
 ;CHECK:               OpStore %139 %93
-;CHECK:               OpBranch %105
-;CHECK:        %105 = OpLabel
+;CHECK:               OpBranch %104
+;CHECK:        %104 = OpLabel
 ;CHECK:               OpReturn
 ;CHECK:               OpFunctionEnd
  )";
@@ -8557,6 +8559,7 @@
 %_ptr_Input_v4float = OpTypePointer Input %v4float
  %a_position = OpVariable %_ptr_Input_v4float Input
 ;CHECK;     %uint_0 = OpConstant %uint 0
+;CHECK;     %uint_16 = OpConstant %uint 16
 ;CHECK;     %uint_4 = OpConstant %uint 4
 ;CHECK;     %uint_3 = OpConstant %uint 3
 ;CHECK;         %37 = OpTypeFunction %uint %uint %uint %uint
@@ -8583,10 +8586,13 @@
 ;CHECK;    %uint_10 = OpConstant %uint 10
 ;CHECK;    %uint_45 = OpConstant %uint 45
 ;CHECK;        %115 = OpConstantNull %float
-;CHECK;    %uint_27 = OpConstant %uint 27
        %main = OpFunction %void None %3
           %5 = OpLabel
 ;CHECK:         %55 = OpFunctionCall %uint %36 %uint_1 %uint_0 %uint_0
+;CHECK:               OpBranch %26
+;CHECK:         %26 = OpLabel
+;CHECK:               OpBranch %25
+;CHECK:         %25 = OpLabel
          %20 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_2 %uint_1
          %21 = OpLoad %float %20
 ;CHECK-NOT:     %21 = OpLoad %float %20
@@ -8602,7 +8608,7 @@
 ;CHECK:         %61 = OpLoad %float %20
 ;CHECK:               OpBranch %58
 ;CHECK:         %60 = OpLabel
-;CHECK:        %114 = OpFunctionCall %void %62 %uint_45 %uint_3 %uint_0 %35 %55
+;CHECK:        %114 = OpFunctionCall %void %62 %uint_45 %uint_4 %uint_0 %35 %55
 ;CHECK:               OpBranch %58
 ;CHECK:         %58 = OpLabel
 ;CHECK:        %116 = OpPhi %float %61 %59 %115 %60
@@ -8790,9 +8796,14 @@
 ;CHECK:     %uint_9 = OpConstant %uint 9
 ;CHECK:    %uint_10 = OpConstant %uint 10
 ;CHECK:    %uint_45 = OpConstant %uint 45
+;CHECK:        %114 = OpConstantNull %float
 %main = OpFunction %void None %3
           %5 = OpLabel
 ;CHECK:         %55 = OpFunctionCall %uint %36 %uint_1 %uint_0 %uint_0
+;CHECK:               OpBranch %26
+;CHECK:         %26 = OpLabel
+;CHECK:               OpBranch %25
+;CHECK:         %25 = OpLabel
          %20 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_2 %uint_1
          %21 = OpLoad %float %20
 ;CHECK-NOT:     %21 = OpLoad %float %20
@@ -8808,7 +8819,7 @@
 ;CHECK:         %61 = OpLoad %float %20
 ;CHECK:               OpBranch %58
 ;CHECK:         %60 = OpLabel
-;CHECK:        %113 = OpFunctionCall %void %62 %uint_45 %uint_3 %uint_0 %35 %55
+;CHECK:        %113 = OpFunctionCall %void %62 %uint_45 %uint_4 %uint_0 %35 %55
 ;CHECK:               OpBranch %58
 ;CHECK:         %58 = OpLabel
 ;CHECK:        %115 = OpPhi %float %61 %59 %114 %60
@@ -9032,7 +9043,7 @@
 ;CHECK:         %70 = OpLoad %v2float %25
 ;CHECK:               OpBranch %67
 ;CHECK:         %69 = OpLabel
-;CHECK:        %123 = OpFunctionCall %void %71 %uint_51 %uint_3 %uint_0 %43 %64
+;CHECK:        %123 = OpFunctionCall %void %71 %uint_51 %uint_4 %uint_0 %43 %64
 ;CHECK:               OpBranch %67
 ;CHECK:         %67 = OpLabel
 ;CHECK:        %125 = OpPhi %v2float %70 %68 %124 %69
@@ -9167,7 +9178,7 @@
 ;CHECK:           %uint = OpTypeInt 32 0
 ;CHECK:         %uint_0 = OpConstant %uint 0
 ;CHECK:           %bool = OpTypeBool
-;CHECK:         %uint_3 = OpConstant %uint 3
+;CHECK:         %uint_7 = OpConstant %uint 7
 ;CHECK:             %35 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
 ;CHECK:     %_struct_43 = OpTypeStruct %uint %_runtimearr_uint
@@ -9179,11 +9190,11 @@
 ;CHECK:         %uint_1 = OpConstant %uint 1
 ;CHECK:        %uint_23 = OpConstant %uint 23
 ;CHECK:         %uint_2 = OpConstant %uint 2
+;CHECK:         %uint_3 = OpConstant %uint 3
 ;CHECK:    %_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:    %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:         %v4uint = OpTypeVector %uint 4
 ;CHECK:         %uint_5 = OpConstant %uint 5
-;CHECK:         %uint_7 = OpConstant %uint 7
 ;CHECK:         %uint_8 = OpConstant %uint 8
 ;CHECK:         %uint_9 = OpConstant %uint 9
 ;CHECK:        %uint_10 = OpConstant %uint 10
@@ -9213,7 +9224,7 @@
 ;CHECK:             %33 = OpImageRead %v4float %32 %17
 ;CHECK:                   OpBranch %29
 ;CHECK:             %31 = OpLabel
-;CHECK:             %92 = OpFunctionCall %void %34 %uint_33 %uint_3 %uint_0 %23 %25
+;CHECK:             %92 = OpFunctionCall %void %34 %uint_33 %uint_7 %uint_0 %23 %25
 ;CHECK:                   OpBranch %29
 ;CHECK:             %29 = OpLabel
 ;CHECK:             %94 = OpPhi %v4float %33 %30 %93 %31
@@ -9244,19 +9255,19 @@
 ;CHECK:             %63 = OpIAdd %uint %50 %uint_2
 ;CHECK:             %64 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %63
 ;CHECK:                   OpStore %64 %36
-;CHECK:             %65 = OpIAdd %uint %50 %uint_3
-;CHECK:             %66 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %65
-;CHECK:                   OpStore %66 %uint_4
-;CHECK:             %69 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %71 = OpBitcast %v4uint %69
-;CHECK:             %72 = OpCompositeExtract %uint %71 0
-;CHECK:             %73 = OpIAdd %uint %50 %uint_4
-;CHECK:             %74 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %73
-;CHECK:                   OpStore %74 %72
-;CHECK:             %75 = OpCompositeExtract %uint %71 1
-;CHECK:             %77 = OpIAdd %uint %50 %uint_5
-;CHECK:             %78 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %77
-;CHECK:                   OpStore %78 %75
+;CHECK:             %66 = OpIAdd %uint %50 %uint_3
+;CHECK:             %67 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %66
+;CHECK:                   OpStore %67 %uint_4
+;CHECK:             %70 = OpLoad %v4float %gl_FragCoord
+;CHECK:             %72 = OpBitcast %v4uint %70
+;CHECK:             %73 = OpCompositeExtract %uint %72 0
+;CHECK:             %74 = OpIAdd %uint %50 %uint_4
+;CHECK:             %75 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %74
+;CHECK:                   OpStore %75 %73
+;CHECK:             %76 = OpCompositeExtract %uint %72 1
+;CHECK:             %78 = OpIAdd %uint %50 %uint_5
+;CHECK:             %79 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %78
+;CHECK:                   OpStore %79 %76
 ;CHECK:             %80 = OpIAdd %uint %50 %uint_7
 ;CHECK:             %81 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %80
 ;CHECK:                   OpStore %81 %37
@@ -9336,7 +9347,7 @@
 ;CHECK:           %uint = OpTypeInt 32 0
 ;CHECK:         %uint_0 = OpConstant %uint 0
 ;CHECK:           %bool = OpTypeBool
-;CHECK:         %uint_3 = OpConstant %uint 3
+;CHECK:         %uint_7 = OpConstant %uint 7
 ;CHECK:             %34 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
 ;CHECK:     %_struct_42 = OpTypeStruct %uint %_runtimearr_uint
@@ -9348,11 +9359,11 @@
 ;CHECK:         %uint_1 = OpConstant %uint 1
 ;CHECK:        %uint_23 = OpConstant %uint 23
 ;CHECK:         %uint_2 = OpConstant %uint 2
+;CHECK:         %uint_3 = OpConstant %uint 3
 ;CHECK:    %_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:    %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:         %v4uint = OpTypeVector %uint 4
 ;CHECK:         %uint_5 = OpConstant %uint 5
-;CHECK:         %uint_7 = OpConstant %uint 7
 ;CHECK:         %uint_8 = OpConstant %uint 8
 ;CHECK:         %uint_9 = OpConstant %uint 9
 ;CHECK:        %uint_10 = OpConstant %uint 10
@@ -9380,7 +9391,7 @@
 ;CHECK:                   OpImageWrite %32 %14 %18
 ;CHECK:                   OpBranch %29
 ;CHECK:             %31 = OpLabel
-;CHECK:             %91 = OpFunctionCall %void %33 %uint_34 %uint_3 %uint_0 %23 %25
+;CHECK:             %91 = OpFunctionCall %void %33 %uint_34 %uint_7 %uint_0 %23 %25
 ;CHECK:                   OpBranch %29
 ;CHECK:             %29 = OpLabel
                           OpReturn
@@ -9409,19 +9420,19 @@
 ;CHECK:             %62 = OpIAdd %uint %49 %uint_2
 ;CHECK:             %63 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %62
 ;CHECK:                   OpStore %63 %35
-;CHECK:             %64 = OpIAdd %uint %49 %uint_3
-;CHECK:             %65 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %64
-;CHECK:                   OpStore %65 %uint_4
-;CHECK:             %68 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %70 = OpBitcast %v4uint %68
-;CHECK:             %71 = OpCompositeExtract %uint %70 0
-;CHECK:             %72 = OpIAdd %uint %49 %uint_4
-;CHECK:             %73 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %72
-;CHECK:                   OpStore %73 %71
-;CHECK:             %74 = OpCompositeExtract %uint %70 1
-;CHECK:             %76 = OpIAdd %uint %49 %uint_5
-;CHECK:             %77 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %76
-;CHECK:                   OpStore %77 %74
+;CHECK:             %65 = OpIAdd %uint %49 %uint_3
+;CHECK:             %66 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %65
+;CHECK:                   OpStore %66 %uint_4
+;CHECK:             %69 = OpLoad %v4float %gl_FragCoord
+;CHECK:             %71 = OpBitcast %v4uint %69
+;CHECK:             %72 = OpCompositeExtract %uint %71 0
+;CHECK:             %73 = OpIAdd %uint %49 %uint_4
+;CHECK:             %74 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %73
+;CHECK:                   OpStore %74 %72
+;CHECK:             %75 = OpCompositeExtract %uint %71 1
+;CHECK:             %77 = OpIAdd %uint %49 %uint_5
+;CHECK:             %78 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %77
+;CHECK:                   OpStore %78 %75
 ;CHECK:             %79 = OpIAdd %uint %49 %uint_7
 ;CHECK:             %80 = OpAccessChain %_ptr_StorageBuffer_uint %44 %uint_1 %79
 ;CHECK:                   OpStore %80 %36
@@ -9500,7 +9511,7 @@
 ;CHECK:           %uint = OpTypeInt 32 0
 ;CHECK:         %uint_0 = OpConstant %uint 0
 ;CHECK:           %bool = OpTypeBool
-;CHECK:         %uint_3 = OpConstant %uint 3
+;CHECK:         %uint_6 = OpConstant %uint 6
 ;CHECK:             %35 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
 ;CHECK:     %_struct_43 = OpTypeStruct %uint %_runtimearr_uint
@@ -9512,6 +9523,7 @@
 ;CHECK:         %uint_1 = OpConstant %uint 1
 ;CHECK:        %uint_23 = OpConstant %uint 23
 ;CHECK:         %uint_2 = OpConstant %uint 2
+;CHECK:         %uint_3 = OpConstant %uint 3
 ;CHECK:    %_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:    %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:         %v4uint = OpTypeVector %uint 4
@@ -9521,7 +9533,7 @@
 ;CHECK:         %uint_9 = OpConstant %uint 9
 ;CHECK:        %uint_10 = OpConstant %uint 10
 ;CHECK:        %uint_32 = OpConstant %uint 32
-;CHECK:             %93 = OpConstantNull %v4float
+;CHECK:             %94 = OpConstantNull %v4float
                   %main = OpFunction %void None %3
                      %5 = OpLabel
 ;CHECK:                   OpBranch %21
@@ -9546,11 +9558,11 @@
 ;CHECK:             %33 = OpImageFetch %v4float %32 %17
 ;CHECK:                   OpBranch %29
 ;CHECK:             %31 = OpLabel
-;CHECK:             %92 = OpFunctionCall %void %34 %uint_32 %uint_3 %uint_0 %23 %25
+;CHECK:             %93 = OpFunctionCall %void %34 %uint_32 %uint_6 %uint_0 %23 %25
 ;CHECK:                   OpBranch %29
 ;CHECK:             %29 = OpLabel
-;CHECK:             %94 = OpPhi %v4float %33 %30 %93 %31
-;CHECK:                   OpStore %x %94
+;CHECK:             %95 = OpPhi %v4float %33 %30 %94 %31
+;CHECK:                   OpStore %x %95
                           OpReturn
                           OpFunctionEnd
 ;CHECK:             %34 = OpFunction %void None %35
@@ -9577,31 +9589,31 @@
 ;CHECK:             %63 = OpIAdd %uint %50 %uint_2
 ;CHECK:             %64 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %63
 ;CHECK:                   OpStore %64 %36
-;CHECK:             %65 = OpIAdd %uint %50 %uint_3
-;CHECK:             %66 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %65
-;CHECK:                   OpStore %66 %uint_4
-;CHECK:             %69 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %71 = OpBitcast %v4uint %69
-;CHECK:             %72 = OpCompositeExtract %uint %71 0
-;CHECK:             %73 = OpIAdd %uint %50 %uint_4
-;CHECK:             %74 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %73
-;CHECK:                   OpStore %74 %72
-;CHECK:             %75 = OpCompositeExtract %uint %71 1
-;CHECK:             %77 = OpIAdd %uint %50 %uint_5
-;CHECK:             %78 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %77
-;CHECK:                   OpStore %78 %75
-;CHECK:             %80 = OpIAdd %uint %50 %uint_7
-;CHECK:             %81 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %80
-;CHECK:                   OpStore %81 %37
-;CHECK:             %83 = OpIAdd %uint %50 %uint_8
-;CHECK:             %84 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %83
-;CHECK:                   OpStore %84 %38
-;CHECK:             %86 = OpIAdd %uint %50 %uint_9
-;CHECK:             %87 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %86
-;CHECK:                   OpStore %87 %39
-;CHECK:             %89 = OpIAdd %uint %50 %uint_10
-;CHECK:             %90 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %89
-;CHECK:                   OpStore %90 %40
+;CHECK:             %66 = OpIAdd %uint %50 %uint_3
+;CHECK:             %67 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %66
+;CHECK:                   OpStore %67 %uint_4
+;CHECK:             %70 = OpLoad %v4float %gl_FragCoord
+;CHECK:             %72 = OpBitcast %v4uint %70
+;CHECK:             %73 = OpCompositeExtract %uint %72 0
+;CHECK:             %74 = OpIAdd %uint %50 %uint_4
+;CHECK:             %75 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %74
+;CHECK:                   OpStore %75 %73
+;CHECK:             %76 = OpCompositeExtract %uint %72 1
+;CHECK:             %78 = OpIAdd %uint %50 %uint_5
+;CHECK:             %79 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %78
+;CHECK:                   OpStore %79 %76
+;CHECK:             %81 = OpIAdd %uint %50 %uint_7
+;CHECK:             %82 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %81
+;CHECK:                   OpStore %82 %37
+;CHECK:             %84 = OpIAdd %uint %50 %uint_8
+;CHECK:             %85 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %84
+;CHECK:                   OpStore %85 %38
+;CHECK:             %87 = OpIAdd %uint %50 %uint_9
+;CHECK:             %88 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %87
+;CHECK:                   OpStore %88 %39
+;CHECK:             %90 = OpIAdd %uint %50 %uint_10
+;CHECK:             %91 = OpAccessChain %_ptr_StorageBuffer_uint %45 %uint_1 %90
+;CHECK:                   OpStore %91 %40
 ;CHECK:                   OpBranch %54
 ;CHECK:             %54 = OpLabel
 ;CHECK:                   OpReturn
@@ -9669,7 +9681,7 @@
 ;CHECK:           %uint = OpTypeInt 32 0
 ;CHECK:         %uint_0 = OpConstant %uint 0
 ;CHECK:           %bool = OpTypeBool
-;CHECK:         %uint_3 = OpConstant %uint 3
+;CHECK:         %uint_6 = OpConstant %uint 6
 ;CHECK:             %38 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
 ;CHECK:     %_struct_46 = OpTypeStruct %uint %_runtimearr_uint
@@ -9681,6 +9693,7 @@
 ;CHECK:         %uint_1 = OpConstant %uint 1
 ;CHECK:        %uint_23 = OpConstant %uint 23
 ;CHECK:         %uint_2 = OpConstant %uint 2
+;CHECK:         %uint_3 = OpConstant %uint 3
 ;CHECK:    %_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:    %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:         %v4uint = OpTypeVector %uint 4
@@ -9690,7 +9703,7 @@
 ;CHECK:         %uint_9 = OpConstant %uint 9
 ;CHECK:        %uint_10 = OpConstant %uint 10
 ;CHECK:        %uint_34 = OpConstant %uint 34
-;CHECK:             %96 = OpConstantNull %v4float
+;CHECK:             %97 = OpConstantNull %v4float
                   %main = OpFunction %void None %3
                      %5 = OpLabel
 ;CHECK:                   OpBranch %23
@@ -9717,11 +9730,11 @@
 ;CHECK:             %36 = OpImageFetch %v4float %35 %18
 ;CHECK:                   OpBranch %31
 ;CHECK:             %33 = OpLabel
-;CHECK:             %95 = OpFunctionCall %void %37 %uint_34 %uint_3 %uint_0 %25 %27
+;CHECK:             %96 = OpFunctionCall %void %37 %uint_34 %uint_6 %uint_0 %25 %27
 ;CHECK:                   OpBranch %31
 ;CHECK:             %31 = OpLabel
-;CHECK:             %97 = OpPhi %v4float %36 %32 %96 %33
-;CHECK:                   OpStore %x %97
+;CHECK:             %98 = OpPhi %v4float %36 %32 %97 %33
+;CHECK:                   OpStore %x %98
                           OpReturn
                           OpFunctionEnd
 ;CHECK:             %37 = OpFunction %void None %38
@@ -9748,31 +9761,31 @@
 ;CHECK:             %66 = OpIAdd %uint %53 %uint_2
 ;CHECK:             %67 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %66
 ;CHECK:                   OpStore %67 %39
-;CHECK:             %68 = OpIAdd %uint %53 %uint_3
-;CHECK:             %69 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %68
-;CHECK:                   OpStore %69 %uint_4
-;CHECK:             %72 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %74 = OpBitcast %v4uint %72
-;CHECK:             %75 = OpCompositeExtract %uint %74 0
-;CHECK:             %76 = OpIAdd %uint %53 %uint_4
-;CHECK:             %77 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %76
-;CHECK:                   OpStore %77 %75
-;CHECK:             %78 = OpCompositeExtract %uint %74 1
-;CHECK:             %80 = OpIAdd %uint %53 %uint_5
-;CHECK:             %81 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %80
-;CHECK:                   OpStore %81 %78
-;CHECK:             %83 = OpIAdd %uint %53 %uint_7
-;CHECK:             %84 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %83
-;CHECK:                   OpStore %84 %40
-;CHECK:             %86 = OpIAdd %uint %53 %uint_8
-;CHECK:             %87 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %86
-;CHECK:                   OpStore %87 %41
-;CHECK:             %89 = OpIAdd %uint %53 %uint_9
-;CHECK:             %90 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %89
-;CHECK:                   OpStore %90 %42
-;CHECK:             %92 = OpIAdd %uint %53 %uint_10
-;CHECK:             %93 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %92
-;CHECK:                   OpStore %93 %43
+;CHECK:             %69 = OpIAdd %uint %53 %uint_3
+;CHECK:             %70 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %69
+;CHECK:                   OpStore %70 %uint_4
+;CHECK:             %73 = OpLoad %v4float %gl_FragCoord
+;CHECK:             %75 = OpBitcast %v4uint %73
+;CHECK:             %76 = OpCompositeExtract %uint %75 0
+;CHECK:             %77 = OpIAdd %uint %53 %uint_4
+;CHECK:             %78 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %77
+;CHECK:                   OpStore %78 %76
+;CHECK:             %79 = OpCompositeExtract %uint %75 1
+;CHECK:             %81 = OpIAdd %uint %53 %uint_5
+;CHECK:             %82 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %81
+;CHECK:                   OpStore %82 %79
+;CHECK:             %84 = OpIAdd %uint %53 %uint_7
+;CHECK:             %85 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %84
+;CHECK:                   OpStore %85 %40
+;CHECK:             %87 = OpIAdd %uint %53 %uint_8
+;CHECK:             %88 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %87
+;CHECK:                   OpStore %88 %41
+;CHECK:             %90 = OpIAdd %uint %53 %uint_9
+;CHECK:             %91 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %90
+;CHECK:                   OpStore %91 %42
+;CHECK:             %93 = OpIAdd %uint %53 %uint_10
+;CHECK:             %94 = OpAccessChain %_ptr_StorageBuffer_uint %48 %uint_1 %93
+;CHECK:                   OpStore %94 %43
 ;CHECK:                   OpBranch %57
 ;CHECK:             %57 = OpLabel
 ;CHECK:                   OpReturn
@@ -9847,7 +9860,7 @@
 ;CHECK:           %uint = OpTypeInt 32 0
 ;CHECK:         %uint_0 = OpConstant %uint 0
 ;CHECK:           %bool = OpTypeBool
-;CHECK:         %uint_3 = OpConstant %uint 3
+;CHECK:         %uint_6 = OpConstant %uint 6
 ;CHECK:             %44 = OpTypeFunction %void %uint %uint %uint %uint %uint
 ;CHECK:    %_runtimearr_uint = OpTypeRuntimeArray %uint
 ;CHECK:     %_struct_52 = OpTypeStruct %uint %_runtimearr_uint
@@ -9859,6 +9872,7 @@
 ;CHECK:         %uint_1 = OpConstant %uint 1
 ;CHECK:        %uint_23 = OpConstant %uint 23
 ;CHECK:         %uint_2 = OpConstant %uint 2
+;CHECK:         %uint_3 = OpConstant %uint 3
 ;CHECK:    %_ptr_Input_v4float = OpTypePointer Input %v4float
 ;CHECK:    %gl_FragCoord = OpVariable %_ptr_Input_v4float Input
 ;CHECK:         %v4uint = OpTypeVector %uint 4
@@ -9868,7 +9882,7 @@
 ;CHECK:         %uint_9 = OpConstant %uint 9
 ;CHECK:        %uint_10 = OpConstant %uint 10
 ;CHECK:        %uint_42 = OpConstant %uint 42
-;CHECK:            %102 = OpConstantNull %v4float
+;CHECK:            %103 = OpConstantNull %v4float
                   %main = OpFunction %void None %3
                      %5 = OpLabel
 ;CHECK:                   OpBranch %28
@@ -9898,11 +9912,11 @@
 ;CHECK:             %42 = OpImageFetch %v4float %41 %23
 ;CHECK:                   OpBranch %36
 ;CHECK:             %38 = OpLabel
-;CHECK:            %101 = OpFunctionCall %void %43 %uint_42 %uint_3 %uint_0 %30 %32
+;CHECK:            %102 = OpFunctionCall %void %43 %uint_42 %uint_6 %uint_0 %30 %32
 ;CHECK:                   OpBranch %36
 ;CHECK:             %36 = OpLabel
-;CHECK:            %103 = OpPhi %v4float %42 %37 %102 %38
-;CHECK:                   OpStore %x %103
+;CHECK:            %104 = OpPhi %v4float %42 %37 %103 %38
+;CHECK:                   OpStore %x %104
                           OpReturn
                           OpFunctionEnd
 ;CHECK:             %43 = OpFunction %void None %44
@@ -9929,31 +9943,31 @@
 ;CHECK:             %72 = OpIAdd %uint %59 %uint_2
 ;CHECK:             %73 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %72
 ;CHECK:                   OpStore %73 %45
-;CHECK:             %74 = OpIAdd %uint %59 %uint_3
-;CHECK:             %75 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %74
-;CHECK:                   OpStore %75 %uint_4
-;CHECK:             %78 = OpLoad %v4float %gl_FragCoord
-;CHECK:             %80 = OpBitcast %v4uint %78
-;CHECK:             %81 = OpCompositeExtract %uint %80 0
-;CHECK:             %82 = OpIAdd %uint %59 %uint_4
-;CHECK:             %83 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %82
-;CHECK:                   OpStore %83 %81
-;CHECK:             %84 = OpCompositeExtract %uint %80 1
-;CHECK:             %86 = OpIAdd %uint %59 %uint_5
-;CHECK:             %87 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %86
-;CHECK:                   OpStore %87 %84
-;CHECK:             %89 = OpIAdd %uint %59 %uint_7
-;CHECK:             %90 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %89
-;CHECK:                   OpStore %90 %46
-;CHECK:             %92 = OpIAdd %uint %59 %uint_8
-;CHECK:             %93 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %92
-;CHECK:                   OpStore %93 %47
-;CHECK:             %95 = OpIAdd %uint %59 %uint_9
-;CHECK:             %96 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %95
-;CHECK:                   OpStore %96 %48
-;CHECK:             %98 = OpIAdd %uint %59 %uint_10
-;CHECK:             %99 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %98
-;CHECK:                   OpStore %99 %49
+;CHECK:             %75 = OpIAdd %uint %59 %uint_3
+;CHECK:             %76 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %75
+;CHECK:                   OpStore %76 %uint_4
+;CHECK:             %79 = OpLoad %v4float %gl_FragCoord
+;CHECK:             %81 = OpBitcast %v4uint %79
+;CHECK:             %82 = OpCompositeExtract %uint %81 0
+;CHECK:             %83 = OpIAdd %uint %59 %uint_4
+;CHECK:             %84 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %83
+;CHECK:                   OpStore %84 %82
+;CHECK:             %85 = OpCompositeExtract %uint %81 1
+;CHECK:             %87 = OpIAdd %uint %59 %uint_5
+;CHECK:             %88 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %87
+;CHECK:                   OpStore %88 %85
+;CHECK:             %90 = OpIAdd %uint %59 %uint_7
+;CHECK:             %91 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %90
+;CHECK:                   OpStore %91 %46
+;CHECK:             %93 = OpIAdd %uint %59 %uint_8
+;CHECK:             %94 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %93
+;CHECK:                   OpStore %94 %47
+;CHECK:             %96 = OpIAdd %uint %59 %uint_9
+;CHECK:             %97 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %96
+;CHECK:                   OpStore %97 %48
+;CHECK:             %99 = OpIAdd %uint %59 %uint_10
+;CHECK:             %100 = OpAccessChain %_ptr_StorageBuffer_uint %54 %uint_1 %99
+;CHECK:                   OpStore %100 %49
 ;CHECK:                   OpBranch %63
 ;CHECK:             %63 = OpLabel
 ;CHECK:                   OpReturn
diff --git a/test/opt/inst_buff_addr_check_test.cpp b/test/opt/inst_buff_addr_check_test.cpp
index 41ead67..95114b2 100644
--- a/test/opt/inst_buff_addr_check_test.cpp
+++ b/test/opt/inst_buff_addr_check_test.cpp
@@ -615,6 +615,212 @@
       true, 7u, 23u);
 }
 
+TEST_F(InstBuffAddrTest, StructLoad) {
+  // #version 450
+  //   #extension GL_EXT_buffer_reference : enable
+  //   #extension GL_ARB_gpu_shader_int64 : enable
+  //   struct Test {
+  //   float a;
+  // };
+  //
+  // layout(buffer_reference, std430, buffer_reference_align = 16) buffer
+  // TestBuffer { Test test; };
+  //
+  // Test GetTest(uint64_t ptr) {
+  //   return TestBuffer(ptr).test;
+  // }
+  //
+  // void main() {
+  //   GetTest(0xe0000000);
+  // }
+
+  const std::string defs =
+      R"(
+OpCapability Shader
+OpCapability Int64
+OpCapability PhysicalStorageBufferAddresses
+; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+; CHECK: OpEntryPoint Fragment %main "main" %60 %99 %gl_FragCoord
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpSourceExtension "GL_ARB_gpu_shader_int64"
+OpSourceExtension "GL_EXT_buffer_reference"
+OpName %main "main"
+OpName %Test "Test"
+OpMemberName %Test 0 "a"
+OpName %Test_0 "Test"
+OpMemberName %Test_0 0 "a"
+OpName %TestBuffer "TestBuffer"
+OpMemberName %TestBuffer 0 "test"
+)";
+
+  const std::string decorates =
+      R"(
+OpMemberDecorate %Test_0 0 Offset 0
+OpMemberDecorate %TestBuffer 0 Offset 0
+OpDecorate %TestBuffer Block
+; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8
+; CHECK: OpDecorate %_struct_58 Block
+; CHECK: OpMemberDecorate %_struct_58 0 Offset 0
+; CHECK: OpDecorate %60 DescriptorSet 7
+; CHECK: OpDecorate %60 Binding 2
+; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4
+; CHECK: OpDecorate %_struct_97 Block
+; CHECK: OpMemberDecorate %_struct_97 0 Offset 0
+; CHECK: OpMemberDecorate %_struct_97 1 Offset 4
+; CHECK: OpDecorate %99 DescriptorSet 7
+; CHECK: OpDecorate %99 Binding 0
+; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord
+)";
+
+  const std::string globals =
+      R"(
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%ulong = OpTypeInt 64 0
+%float = OpTypeFloat 32
+%Test = OpTypeStruct %float
+OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_TestBuffer PhysicalStorageBuffer
+%Test_0 = OpTypeStruct %float
+%TestBuffer = OpTypeStruct %Test_0
+%_ptr_PhysicalStorageBuffer_TestBuffer = OpTypePointer PhysicalStorageBuffer %TestBuffer
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_PhysicalStorageBuffer_Test_0 = OpTypePointer PhysicalStorageBuffer %Test_0
+%ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704
+; CHECK: %47 = OpTypeFunction %bool %ulong %uint
+; CHECK: %_struct_58 = OpTypeStruct %_runtimearr_ulong
+; CHECK: %60 = OpVariable %_ptr_StorageBuffer__struct_58 StorageBuffer
+; CHECK: %90 = OpTypeFunction %void %uint %uint %uint %uint
+; CHECK: %_struct_97 = OpTypeStruct %uint %_runtimearr_uint
+; CHECK: %99 = OpVariable %_ptr_StorageBuffer__struct_97 StorageBuffer
+; CHECK: %143 = OpConstantNull %Test_0
+)";
+
+  const std::string main =
+      R"(
+%main = OpFunction %void None %3
+%5 = OpLabel
+%37 = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_TestBuffer %ulong_18446744073172680704
+%38 = OpAccessChain %_ptr_PhysicalStorageBuffer_Test_0 %37 %int_0
+%39 = OpLoad %Test_0 %38 Aligned 16
+; CHECK-NOT: %39 = OpLoad %Test_0 %38 Aligned 16
+; CHECK: %43 = OpConvertPtrToU %ulong %38
+; CHECK: %80 = OpFunctionCall %bool %45 %43 %uint_4
+; CHECK: OpSelectionMerge %81 None
+; CHECK: OpBranchConditional %80 %82 %83
+; CHECK: %82 = OpLabel
+; CHECK: %84 = OpLoad %Test_0 %38 Aligned 16
+; CHECK: OpBranch %81
+; CHECK: %83 = OpLabel
+; CHECK: %85 = OpUConvert %uint %43
+; CHECK: %87 = OpShiftRightLogical %ulong %43 %uint_32
+; CHECK: %88 = OpUConvert %uint %87
+; CHECK: %142 = OpFunctionCall %void %89 %uint_37 %uint_2 %85 %88
+; CHECK: OpBranch %81
+; CHECK: %81 = OpLabel
+; CHECK: %144 = OpPhi %Test_0 %84 %82 %143 %83
+%40 = OpCopyLogical %Test %39
+; CHECK-NOT: %40 = OpCopyLogical %Test %39
+; CHECK: %40 = OpCopyLogical %Test %144
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string output_funcs =
+      R"(
+; CHECK: %45 = OpFunction %bool None %47
+; CHECK: %48 = OpFunctionParameter %ulong
+; CHECK: %49 = OpFunctionParameter %uint
+; CHECK: %50 = OpLabel
+; CHECK: OpBranch %51
+; CHECK: %51 = OpLabel
+; CHECK: %53 = OpPhi %uint %uint_1 %50 %54 %52
+; CHECK: OpLoopMerge %56 %52 None
+; CHECK: OpBranch %52
+; CHECK: %52 = OpLabel
+; CHECK: %54 = OpIAdd %uint %53 %uint_1
+; CHECK: %63 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %54
+; CHECK: %64 = OpLoad %ulong %63
+; CHECK: %65 = OpUGreaterThan %bool %64 %48
+; CHECK: OpBranchConditional %65 %56 %51
+; CHECK: %56 = OpLabel
+; CHECK: %66 = OpISub %uint %54 %uint_1
+; CHECK: %67 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %66
+; CHECK: %68 = OpLoad %ulong %67
+; CHECK: %69 = OpISub %ulong %48 %68
+; CHECK: %70 = OpUConvert %ulong %49
+; CHECK: %71 = OpIAdd %ulong %69 %70
+; CHECK: %72 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %uint_0
+; CHECK: %73 = OpLoad %ulong %72
+; CHECK: %74 = OpUConvert %uint %73
+; CHECK: %75 = OpISub %uint %66 %uint_1
+; CHECK: %76 = OpIAdd %uint %75 %74
+; CHECK: %77 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %76
+; CHECK: %78 = OpLoad %ulong %77
+; CHECK: %79 = OpULessThanEqual %bool %71 %78
+; CHECK: OpReturnValue %79
+; CHECK: OpFunctionEnd
+; CHECK: %89 = OpFunction %void None %90
+; CHECK: %91 = OpFunctionParameter %uint
+; CHECK: %92 = OpFunctionParameter %uint
+; CHECK: %93 = OpFunctionParameter %uint
+; CHECK: %94 = OpFunctionParameter %uint
+; CHECK: %95 = OpLabel
+; CHECK: %101 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_0
+; CHECK: %103 = OpAtomicIAdd %uint %101 %uint_4 %uint_0 %uint_10
+; CHECK: %104 = OpIAdd %uint %103 %uint_10
+; CHECK: %105 = OpArrayLength %uint %99 1
+; CHECK: %106 = OpULessThanEqual %bool %104 %105
+; CHECK: OpSelectionMerge %107 None
+; CHECK: OpBranchConditional %106 %108 %107
+; CHECK: %108 = OpLabel
+; CHECK: %109 = OpIAdd %uint %103 %uint_0
+; CHECK: %110 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %109
+; CHECK: OpStore %110 %uint_10
+; CHECK: %112 = OpIAdd %uint %103 %uint_1
+; CHECK: %113 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %112
+; CHECK: OpStore %113 %uint_23
+; CHECK: %114 = OpIAdd %uint %103 %uint_2
+; CHECK: %115 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %114
+; CHECK: OpStore %115 %91
+; CHECK: %117 = OpIAdd %uint %103 %uint_3
+; CHECK: %118 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %117
+; CHECK: OpStore %118 %uint_4
+; CHECK: %122 = OpLoad %v4float %gl_FragCoord
+; CHECK: %124 = OpBitcast %v4uint %122
+; CHECK: %125 = OpCompositeExtract %uint %124 0
+; CHECK: %126 = OpIAdd %uint %103 %uint_4
+; CHECK: %127 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %126
+; CHECK: OpStore %127 %125
+; CHECK: %128 = OpCompositeExtract %uint %124 1
+; CHECK: %130 = OpIAdd %uint %103 %uint_5
+; CHECK: %131 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %130
+; CHECK: OpStore %131 %128
+; CHECK: %133 = OpIAdd %uint %103 %uint_7
+; CHECK: %134 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %133
+; CHECK: OpStore %134 %92
+; CHECK: %136 = OpIAdd %uint %103 %uint_8
+; CHECK: %137 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %136
+; CHECK: OpStore %137 %93
+; CHECK: %139 = OpIAdd %uint %103 %uint_9
+; CHECK: %140 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %139
+; CHECK: OpStore %140 %94
+; CHECK: OpBranch %107
+; CHECK: %107 = OpLabel
+; CHECK: OpReturn
+; CHECK: OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBuffAddrCheckPass>(
+      defs + decorates + globals + main + output_funcs, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/instruction_list_test.cpp b/test/opt/instruction_list_test.cpp
index e745790..2c3c242 100644
--- a/test/opt/instruction_list_test.cpp
+++ b/test/opt/instruction_list_test.cpp
@@ -35,7 +35,7 @@
  public:
   TestInstruction() : Instruction() { created_instructions_.push_back(this); }
 
-  ~TestInstruction() { deleted_instructions_.push_back(this); }
+  ~TestInstruction() override{ deleted_instructions_.push_back(this); }
 
   static std::vector<TestInstruction*> created_instructions_;
   static std::vector<TestInstruction*> deleted_instructions_;
diff --git a/test/opt/interp_fixup_test.cpp b/test/opt/interp_fixup_test.cpp
new file mode 100644
index 0000000..a43a29c
--- /dev/null
+++ b/test/opt/interp_fixup_test.cpp
@@ -0,0 +1,172 @@
+// Copyright (c) 2021 The Khronos Group Inc.
+// Copyright (c) 2021 Valve Corporation
+// Copyright (c) 2021 LunarG Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using InterpFixupTest = PassTest<::testing::Test>;
+
+using ::testing::HasSubstr;
+
+TEST_F(InterpFixupTest, FixInterpAtSample) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability InterpolationFunction
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %i_vPositionOs %_entryPointOutput
+               OpExecutionMode %MainPs OriginUpperLeft
+               OpSource HLSL 500
+               OpName %MainPs "MainPs"
+               OpName %i_vPositionOs "i.vPositionOs"
+               OpName %_entryPointOutput "@entryPointOutput"
+               OpDecorate %i_vPositionOs Location 0
+               OpDecorate %_entryPointOutput Location 0
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %float_0 = OpConstant %float 0
+         %10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+     %uint_4 = OpConstant %uint 4
+       %bool = OpTypeBool
+        %int = OpTypeInt 32 1
+      %int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%i_vPositionOs = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput = OpVariable %_ptr_Output_v4float Output
+     %MainPs = OpFunction %void None %6
+         %19 = OpLabel
+         %20 = OpLoad %v4float %i_vPositionOs
+               OpBranch %21
+         %21 = OpLabel
+         %22 = OpPhi %v4float %10 %19 %23 %24
+         %25 = OpPhi %uint %uint_0 %19 %26 %24
+         %27 = OpULessThan %bool %25 %uint_4
+               OpLoopMerge %28 %24 None
+               OpBranchConditional %27 %24 %28
+         %24 = OpLabel
+         %29 = OpExtInst %v4float %1 InterpolateAtSample %20 %25
+;CHECK:  %29 = OpExtInst %v4float %1 InterpolateAtSample %i_vPositionOs %25
+         %30 = OpCompositeExtract %float %29 0
+         %31 = OpCompositeExtract %float %22 0
+         %32 = OpFAdd %float %31 %30
+         %23 = OpCompositeInsert %v4float %32 %22 0
+         %26 = OpIAdd %uint %25 %int_1
+               OpBranch %21
+         %28 = OpLabel
+               OpStore %_entryPointOutput %22
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<InterpFixupPass>(text, false);
+}
+
+TEST_F(InterpFixupTest, FixInterpAtCentroid) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability InterpolationFunction
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %i_vPositionOs %_entryPointOutput
+               OpExecutionMode %MainPs OriginUpperLeft
+               OpSource HLSL 500
+               OpName %MainPs "MainPs"
+               OpName %i_vPositionOs "i.vPositionOs"
+               OpName %_entryPointOutput "@entryPointOutput"
+               OpDecorate %i_vPositionOs Location 0
+               OpDecorate %_entryPointOutput Location 0
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %float_0 = OpConstant %float 0
+         %10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%i_vPositionOs = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput = OpVariable %_ptr_Output_v4float Output
+     %MainPs = OpFunction %void None %6
+         %13 = OpLabel
+         %14 = OpLoad %v4float %i_vPositionOs
+         %15 = OpExtInst %v4float %1 InterpolateAtCentroid %14
+;CHECK:  %15 = OpExtInst %v4float %1 InterpolateAtCentroid %i_vPositionOs
+         %16 = OpCompositeExtract %float %15 0
+         %17 = OpCompositeInsert %v4float %16 %10 0
+               OpStore %_entryPointOutput %17
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<InterpFixupPass>(text, false);
+}
+
+TEST_F(InterpFixupTest, FixInterpAtOffset) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability InterpolationFunction
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %i_vPositionOs %_entryPointOutput
+               OpExecutionMode %MainPs OriginUpperLeft
+               OpSource HLSL 500
+               OpName %MainPs "MainPs"
+               OpName %i_vPositionOs "i.vPositionOs"
+               OpName %_entryPointOutput "@entryPointOutput"
+               OpDecorate %i_vPositionOs Location 0
+               OpDecorate %_entryPointOutput Location 0
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %float_0 = OpConstant %float 0
+         %10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+    %v2float = OpTypeVector %float 2
+%float_0_0625 = OpConstant %float 0.0625
+         %13 = OpConstantComposite %v2float %float_0_0625 %float_0_0625
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%i_vPositionOs = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput = OpVariable %_ptr_Output_v4float Output
+     %MainPs = OpFunction %void None %6
+         %16 = OpLabel
+         %17 = OpLoad %v4float %i_vPositionOs
+         %18 = OpExtInst %v4float %1 InterpolateAtOffset %17 %13
+;CHECK:  %18 = OpExtInst %v4float %1 InterpolateAtOffset %i_vPositionOs %13
+         %19 = OpCompositeExtract %float %18 0
+         %20 = OpCompositeInsert %v4float %19 %10 0
+               OpStore %_entryPointOutput %20
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<InterpFixupPass>(text, false);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp
index 475dd23..6eafcd9 100644
--- a/test/opt/ir_loader_test.cpp
+++ b/test/opt/ir_loader_test.cpp
@@ -105,6 +105,22 @@
   DoRoundTripCheck("%2 = OpFunction %1 None %3\n");
 }
 
+TEST(IrBuilder, RoundTripFunctionPointer) {
+  DoRoundTripCheck(
+      "OpCapability Linkage\n"
+      "OpCapability FunctionPointersINTEL\n"
+      "OpName %some_function \"some_function\"\n"
+      "OpName %ptr_to_function \"ptr_to_function\"\n"
+      "OpDecorate %some_function LinkageAttributes \"some_function\" Import\n"
+      "%float = OpTypeFloat 32\n"
+      "%4 = OpTypeFunction %float %float\n"
+      "%_ptr_Function_4 = OpTypePointer Function %4\n"
+      "%ptr_to_function = OpConstFunctionPointerINTEL %_ptr_Function_4 "
+      "%some_function\n"
+      "%some_function = OpFunction %float Const %4\n"
+      "%6 = OpFunctionParameter %float\n"
+      "OpFunctionEnd\n");
+}
 TEST(IrBuilder, KeepLineDebugInfo) {
   // #version 310 es
   // void main() {}
diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp
index 8e1cee6..28b8a07 100644
--- a/test/opt/local_single_block_elim.cpp
+++ b/test/opt/local_single_block_elim.cpp
@@ -84,6 +84,56 @@
       predefs_before + before, predefs_before + after, true, true);
 }
 
+TEST_F(LocalSingleBlockLoadStoreElimTest, LSBElimForLinkage) {
+  const std::string predefs_before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %main LinkageAttributes "main" Export
+%void = OpTypeVoid
+%7 = 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_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+%15 = OpLoad %v4float %v
+OpStore %gl_FragColor %15
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpStore %gl_FragColor %14
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+      predefs_before + before, predefs_before + after, true, true);
+}
+
 TEST_F(LocalSingleBlockLoadStoreElimTest, SimpleLoadLoadElim) {
   // #version 140
   //
diff --git a/test/opt/local_single_store_elim_test.cpp b/test/opt/local_single_store_elim_test.cpp
index d015dfb..5d910c4 100644
--- a/test/opt/local_single_store_elim_test.cpp
+++ b/test/opt/local_single_store_elim_test.cpp
@@ -126,6 +126,91 @@
                                                   predefs + after, true, true);
 }
 
+TEST_F(LocalSingleStoreElimTest, LSSElimForLinkage) {
+  const std::string predefs =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %f "f"
+OpName %fi "fi"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %main LinkageAttributes "main" Export
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+OpStore %f %21
+%22 = OpLoad %float %f
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+OpStore %f %float_0
+OpBranch %24
+%24 = OpLabel
+%26 = OpLoad %v4float %v
+%27 = OpLoad %float %f
+%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
+%29 = OpFAdd %v4float %26 %28
+OpStore %gl_FragColor %29
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+OpStore %f %21
+%22 = OpLoad %float %f
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+OpStore %f %float_0
+OpBranch %24
+%24 = OpLabel
+%27 = OpLoad %float %f
+%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
+%29 = OpFAdd %v4float %20 %28
+OpStore %gl_FragColor %29
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+                                                  predefs + after, true, true);
+}
+
 TEST_F(LocalSingleStoreElimTest, ThreeStores) {
   // Three stores to multiple loads of v is not optimized.
 
@@ -1403,18 +1488,20 @@
 %param_var_pos = OpVariable %_ptr_Function_v4float Function
 %param_var_color = OpVariable %_ptr_Function_v4float Function
          %55 = OpLoad %v4float %in_var_POSITION
+               OpLine %7 6 23
                OpStore %param_var_pos %55
+               OpNoLine
          %56 = OpLoad %v4float %in_var_COLOR
 ;CHECK:      DebugNoScope
 ;CHECK-NOT:  OpLine
 ;CHECK:      [[pos:%\w+]] = OpLoad %v4float %in_var_POSITION
 ;CHECK:      [[color:%\w+]] = OpLoad %v4float %in_var_COLOR
 
-               OpStore %param_var_color %56
-         %93 = OpExtInst %void %1 DebugScope %48
-               OpLine %7 6 23
-         %73 = OpExtInst %void %1 DebugDeclare %53 %param_var_pos %52
                OpLine %7 7 23
+               OpStore %param_var_color %56
+               OpNoLine
+         %93 = OpExtInst %void %1 DebugScope %48
+         %73 = OpExtInst %void %1 DebugDeclare %53 %param_var_pos %52
          %74 = OpExtInst %void %1 DebugDeclare %51 %param_var_color %52
 ;CHECK:      OpLine [[file:%\w+]] 6 23
 ;CHECK-NEXT: {{%\w+}} = OpExtInst %void {{%\w+}} DebugValue [[dbg_pos]] [[pos]] [[empty_expr:%\w+]]
diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp
index 9baf8da..4b7542f 100644
--- a/test/opt/local_ssa_elim_test.cpp
+++ b/test/opt/local_ssa_elim_test.cpp
@@ -2165,6 +2165,140 @@
   SinglePassRunAndMatch<SSARewritePass>(text, true);
 }
 
+TEST_F(LocalSSAElimTest, ShaderDebugForLoop) {
+  const std::string text = R"(
+; CHECK: [[f_name:%\w+]] = OpString "f"
+; CHECK: [[i_name:%\w+]] = OpString "i"
+; CHECK: [[dbg_f:%\w+]] = OpExtInst %void [[ext:%\d+]] DebugLocalVariable [[f_name]]
+; CHECK: [[dbg_i:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[i_name]]
+
+; CHECK:      OpStore %f %float_0
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] %float_0
+; CHECK-NEXT: OpStore %i %int_0
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_i]] %int_0
+
+; CHECK-NOT:  DebugDeclare
+
+; CHECK:      [[loop_head:%\w+]] = OpLabel
+; CHECK:      [[phi0:%\w+]] = OpPhi %float %float_0
+; CHECK:      [[phi1:%\w+]] = OpPhi %int %int_0
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] [[phi0]]
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_i]] [[phi1]]
+; CHECK:      OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[loop_body:%\w+]]
+
+; CHECK-NEXT: [[loop_body]] = OpLabel
+; CHECK:      OpBranchConditional {{%\w+}} [[bb:%\w+]] [[loop_merge]]
+
+; CHECK:      [[bb]] = OpLabel
+; CHECK:      OpStore %f [[f_val:%\w+]]
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] [[f_val]]
+; CHECK-NEXT: OpBranch [[loop_cont]]
+
+; CHECK:      [[loop_cont]] = OpLabel
+; CHECK:      OpStore %i [[i_val:%\w+]]
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_i]] [[i_val]]
+; CHECK-NEXT: OpBranch [[loop_head]]
+
+; CHECK:      [[loop_merge]] = OpLabel
+
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "GLSL.std.450"
+%ext = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %fo
+OpExecutionMode %main OriginUpperLeft
+%file_name = OpString "test"
+OpSource GLSL 140
+%float_name = OpString "float"
+%main_name = OpString "main"
+%f_name = OpString "f"
+%i_name = OpString "i"
+OpName %main "main"
+OpName %f "f"
+OpName %i "i"
+OpName %BC "BC"
+OpName %fo "fo"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_10 = OpConstant %uint 10
+%uint_32 = OpConstant %uint 32
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BC = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+%null_expr = OpExtInst %void %ext DebugExpression
+%src = OpExtInst %void %ext DebugSource %file_name
+%cu = OpExtInst %void %ext DebugCompilationUnit %uint_1 %uint_4 %src %uint_5
+%dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 %uint_3 %uint_0
+%dbg_v4f = OpExtInst %void %ext DebugTypeVector %dbg_tf %uint_4
+%main_ty = OpExtInst %void %ext DebugTypeFunction %uint_3 %dbg_v4f %dbg_v4f
+%dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src %uint_0 %uint_0 %cu %main_name %uint_3 %uint_10
+%dbg_f = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_v4f %src %uint_0 %uint_0 %dbg_main %uint_4
+%dbg_i = OpExtInst %void %ext DebugLocalVariable %i_name %dbg_v4f %src %uint_0 %uint_0 %dbg_main %uint_4
+%main = OpFunction %void None %8
+%22 = OpLabel
+%s0 = OpExtInst %void %ext DebugScope %dbg_main
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %f %float_0
+OpStore %i %int_0
+%decl0 = OpExtInst %void %ext DebugDeclare %dbg_f %f %null_expr
+%decl1 = OpExtInst %void %ext DebugDeclare %dbg_i %i %null_expr
+OpBranch %23
+%23 = OpLabel
+%s1 = OpExtInst %void %ext DebugScope %dbg_main
+OpLoopMerge %24 %25 None
+OpBranch %26
+%26 = OpLabel
+%s2 = OpExtInst %void %ext DebugScope %dbg_main
+%27 = OpLoad %int %i
+%28 = OpSLessThan %bool %27 %int_4
+OpBranchConditional %28 %29 %24
+%29 = OpLabel
+%s3 = OpExtInst %void %ext DebugScope %dbg_main
+%30 = OpLoad %float %f
+%31 = OpLoad %int %i
+%32 = OpAccessChain %_ptr_Input_float %BC %31
+%33 = OpLoad %float %32
+%34 = OpFAdd %float %30 %33
+OpStore %f %34
+OpBranch %25
+%25 = OpLabel
+%s4 = OpExtInst %void %ext DebugScope %dbg_main
+%35 = OpLoad %int %i
+%36 = OpIAdd %int %35 %int_1
+OpStore %i %36
+OpBranch %23
+%24 = OpLabel
+%s5 = OpExtInst %void %ext DebugScope %dbg_main
+%37 = OpLoad %float %f
+OpStore %fo %37
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<SSARewritePass>(text, true);
+}
+
 TEST_F(LocalSSAElimTest, AddDebugValueForFunctionParameterWithPhi) {
   // Test the distribution of DebugValue for a parameter of an inlined function
   // and the visibility of Phi instruction. The ssa-rewrite pass must add
@@ -2261,9 +2395,13 @@
          %69 = OpExtInst %void %1 DebugLexicalBlock %60 7 21 %68
          %70 = OpExtInst %void %1 DebugLocalVariable %11 %59 %60 6 26 %67 FlagIsLocal 2
 
+; CHECK: [[color_name:%\w+]] = OpString "color"
+; CHECK: [[pos_name:%\w+]] = OpString "pos"
 ; CHECK: [[i_name:%\w+]] = OpString "i"
 ; CHECK: [[null_expr:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugExpression
 ; CHECK: [[dbg_i:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[i_name]] {{%\w+}} {{%\w+}} 6 16 {{%\w+}} FlagIsLocal 1
+; CHECK: [[dbg_color:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[color_name]] {{%\w+}} {{%\w+}} 15 23
+; CHECK: [[dbg_pos:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[pos_name]] {{%\w+}} {{%\w+}} 14 23
          %71 = OpExtInst %void %1 DebugLocalVariable %15 %65 %60 6 16 %67 FlagIsLocal 1
          %72 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %62 %59 %59
          %73 = OpExtInst %void %1 DebugFunction %16 %72 %60 14 1 %61 %14 FlagIsProtected|FlagIsPrivate 15 %156
@@ -2282,13 +2420,23 @@
         %169 = OpExtInst %void %1 DebugNoScope
 %param_var_pos = OpVariable %_ptr_Function_v4float Function
 %param_var_color = OpVariable %_ptr_Function_v4float Function
+               OpLine %7 100 105
          %80 = OpLoad %v4float %in_var_POSITION
                OpStore %param_var_pos %80
+               OpNoLine
+               OpLine %7 200 205
          %81 = OpLoad %v4float %in_var_COLOR
                OpStore %param_var_color %81
+               OpNoLine
         %170 = OpExtInst %void %1 DebugScope %73
+
+; CHECK: OpLine {{%\w+}} 100 105
+; CHECK: DebugValue [[dbg_pos]]
         %124 = OpExtInst %void %1 DebugDeclare %78 %param_var_pos %77
+; CHECK: OpLine {{%\w+}} 200 205
+; CHECK: DebugValue [[dbg_color]]
         %125 = OpExtInst %void %1 DebugDeclare %76 %param_var_color %77
+
         %171 = OpExtInst %void %1 DebugScope %74
                OpLine %7 17 18
 
@@ -4077,6 +4225,32 @@
   SinglePassRunAndMatch<SSARewritePass>(text, true);
 }
 
+TEST_F(LocalSSAElimTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<SSARewritePass>(text, text, false);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    No optimization in the presence of
diff --git a/test/opt/loop_optimizations/unroll_assumptions.cpp b/test/opt/loop_optimizations/unroll_assumptions.cpp
index 0f93302..159e4a1 100644
--- a/test/opt/loop_optimizations/unroll_assumptions.cpp
+++ b/test/opt/loop_optimizations/unroll_assumptions.cpp
@@ -42,6 +42,10 @@
   Status Process() override {
     bool changed = false;
     for (Function& f : *context()->module()) {
+      if (f.IsDeclaration()) {
+        continue;
+      }
+
       LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(&f);
       for (auto& loop : loop_descriptor) {
         LoopUtils loop_utils{context(), &loop};
@@ -1510,6 +1514,33 @@
   SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
 }
 
+TEST_F(PassClassTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+  SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/loop_optimizations/unroll_simple.cpp b/test/opt/loop_optimizations/unroll_simple.cpp
index 016316a..ac0dfde 100644
--- a/test/opt/loop_optimizations/unroll_simple.cpp
+++ b/test/opt/loop_optimizations/unroll_simple.cpp
@@ -378,6 +378,185 @@
   SinglePassRunAndMatch<LoopUnroller>(text, true);
 }
 
+TEST_F(PassClassTest, SimpleFullyUnrollWithShaderDebugInstructions) {
+  // We must preserve the debug information including
+  // NonSemantic.Shader.DebugInfo.100 instructions and DebugLine instructions.
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "GLSL.std.450"
+%ext = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 330
+%file_name = OpString "test"
+%float_name = OpString "float"
+%main_name = OpString "main"
+%f_name = OpString "f"
+%i_name = OpString "i"
+OpName %2 "main"
+OpName %5 "x"
+OpName %3 "c"
+OpDecorate %3 Location 0
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 4
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%uint_0 = OpConstant %14 0
+%uint_1 = OpConstant %14 1
+%uint_2 = OpConstant %14 2
+%uint_3 = OpConstant %14 3
+%uint_4 = OpConstant %14 4
+%uint_5 = OpConstant %14 5
+%uint_6 = OpConstant %14 6
+%uint_7 = OpConstant %14 7
+%uint_8 = OpConstant %14 8
+%uint_10 = OpConstant %14 10
+%uint_32 = OpConstant %14 32
+%15 = OpConstant %14 4
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpConstant %13 1
+%19 = OpTypePointer Function %13
+%20 = OpConstant %8 1
+%21 = OpTypeVector %13 4
+%22 = OpTypePointer Output %21
+%3 = OpVariable %22 Output
+%null_expr = OpExtInst %6 %ext DebugExpression
+%deref = OpExtInst %6 %ext DebugOperation %uint_0
+%deref_expr = OpExtInst %6 %ext DebugExpression %deref
+%src = OpExtInst %6 %ext DebugSource %file_name
+%cu = OpExtInst %6 %ext DebugCompilationUnit %uint_1 %uint_4 %src %uint_5
+%dbg_tf = OpExtInst %6 %ext DebugTypeBasic %float_name %uint_32 %uint_3 %uint_0
+%dbg_v4f = OpExtInst %6 %ext DebugTypeVector %dbg_tf %uint_4
+%main_ty = OpExtInst %6 %ext DebugTypeFunction %uint_3 %dbg_v4f %dbg_v4f
+%dbg_main = OpExtInst %6 %ext DebugFunction %main_name %main_ty %src %uint_0 %uint_0 %cu %main_name %uint_3 %uint_10
+%bb = OpExtInst %6 %ext DebugLexicalBlock %src %uint_0 %uint_0 %dbg_main
+%dbg_f = OpExtInst %6 %ext DebugLocalVariable %f_name %dbg_v4f %src %uint_0 %uint_0 %dbg_main %uint_4
+%dbg_i = OpExtInst %6 %ext DebugLocalVariable %i_name %dbg_v4f %src %uint_1 %uint_0 %bb %uint_4
+
+; CHECK: [[f:%\w+]] = OpString "f"
+; CHECK: [[i:%\w+]] = OpString "i"
+; CHECK: [[int_0:%\w+]] = OpConstant {{%\w+}} 0
+
+; CHECK: [[null_expr:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugExpression
+; CHECK: [[deref:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugOperation %uint_0
+; CHECK: [[deref_expr:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugExpression [[deref]]
+; CHECK: [[dbg_fn:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugFunction
+; CHECK: [[dbg_bb:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLexicalBlock
+; CHECK: [[dbg_f:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable [[f]] {{%\w+}} {{%\w+}} %uint_0 %uint_0 [[dbg_fn]]
+; CHECK: [[dbg_i:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable [[i]] {{%\w+}} {{%\w+}} %uint_1 %uint_0 [[dbg_bb]]
+
+%2 = OpFunction %6 None %7
+%23 = OpLabel
+
+; The first block has DebugDeclare and DebugValue with Deref
+;
+; CHECK: OpLabel
+; CHECK: %x = OpVariable %_ptr_Function__arr_float_uint_4_0 Function
+; CHECK: OpBranch
+; CHECK: OpLabel
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: DebugValue [[dbg_f]] [[int_0]] [[null_expr]]
+; CHECK: OpBranch
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: DebugLine {{%\w+}} %uint_1 %uint_1 %uint_1 %uint_1
+; CHECK: OpSLessThan
+; CHECK: DebugLine {{%\w+}} %uint_2 %uint_2 %uint_0 %uint_0
+; CHECK: OpBranch
+; CHECK: OpLabel
+; CHECK: DebugScope [[dbg_bb]]
+; CHECK: DebugDeclare [[dbg_f]] %x [[null_expr]]
+; CHECK: DebugValue [[dbg_i]] %x [[deref_expr]]
+; CHECK: DebugLine {{%\w+}} %uint_3 %uint_3 %uint_0 %uint_0
+;
+; CHECK: DebugLine {{%\w+}} %uint_6 %uint_6 %uint_0 %uint_0
+; CHECK: [[add:%\w+]] = OpIAdd
+; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]]
+; CHECK: DebugLine {{%\w+}} %uint_7 %uint_7 %uint_0 %uint_0
+
+; Other blocks do not have DebugDeclare and DebugValue with Deref
+;
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: DebugLine {{%\w+}} %uint_1 %uint_1 %uint_1 %uint_1
+; CHECK: OpSLessThan
+; CHECK: DebugLine {{%\w+}} %uint_2 %uint_2 %uint_0 %uint_0
+; CHECK: OpBranch
+; CHECK: OpLabel
+;
+; CHECK: DebugScope [[dbg_bb]]
+; CHECK-NOT: DebugDeclare [[dbg_f]] %x [[null_expr]]
+; CHECK-NOT: DebugValue [[dbg_i]] %x [[deref_expr]]
+; CHECK: DebugLine {{%\w+}} %uint_3 %uint_3 %uint_0 %uint_0
+;
+; CHECK: DebugLine {{%\w+}} %uint_6 %uint_6 %uint_0 %uint_0
+; CHECK: [[add:%\w+]] = OpIAdd
+; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]]
+; CHECK: DebugLine {{%\w+}} %uint_7 %uint_7 %uint_0 %uint_0
+;
+; CHECK-NOT: DebugDeclare [[dbg_f]] %x [[null_expr]]
+; CHECK-NOT: DebugValue [[dbg_i]] %x [[deref_expr]]
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: DebugLine {{%\w+}} %uint_8 %uint_8 %uint_0 %uint_0
+; CHECK: OpReturn
+
+%5 = OpVariable %17 Function
+OpBranch %24
+%24 = OpLabel
+%35 = OpPhi %8 %10 %23 %34 %26
+%s1 = OpExtInst %6 %ext DebugScope %dbg_main
+%d10 = OpExtInst %6 %ext DebugLine %file_name %uint_1 %uint_1 %uint_0 %uint_0
+%value0 = OpExtInst %6 %ext DebugValue %dbg_f %35 %null_expr
+OpLoopMerge %25 %26 Unroll
+OpBranch %27
+%27 = OpLabel
+%s2 = OpExtInst %6 %ext DebugScope %dbg_main
+%d1 = OpExtInst %6 %ext DebugLine %file_name %uint_1 %uint_1 %uint_1 %uint_1
+%29 = OpSLessThan %12 %35 %11
+%d2 = OpExtInst %6 %ext DebugLine %file_name %uint_2 %uint_2 %uint_0 %uint_0
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+%s3 = OpExtInst %6 %ext DebugScope %bb
+%decl0 = OpExtInst %6 %ext DebugDeclare %dbg_f %5 %null_expr
+%decl1 = OpExtInst %6 %ext DebugValue %dbg_i %5 %deref_expr
+%d3 = OpExtInst %6 %ext DebugLine %file_name %uint_3 %uint_3 %uint_0 %uint_0
+%32 = OpAccessChain %19 %5 %35
+%d4 = OpExtInst %6 %ext DebugLine %file_name %uint_4 %uint_4 %uint_0 %uint_0
+OpStore %32 %18
+%d5 = OpExtInst %6 %ext DebugLine %file_name %uint_5 %uint_5 %uint_0 %uint_0
+OpBranch %26
+%26 = OpLabel
+%s4 = OpExtInst %6 %ext DebugScope %dbg_main
+%d6 = OpExtInst %6 %ext DebugLine %file_name %uint_6 %uint_6 %uint_0 %uint_0
+%34 = OpIAdd %8 %35 %20
+%value1 = OpExtInst %6 %ext DebugValue %dbg_f %34 %null_expr
+%d7 = OpExtInst %6 %ext DebugLine %file_name %uint_7 %uint_7 %uint_0 %uint_0
+OpBranch %24
+%25 = OpLabel
+%s5 = OpExtInst %6 %ext DebugScope %dbg_main
+%d8 = OpExtInst %6 %ext DebugLine %file_name %uint_8 %uint_8 %uint_0 %uint_0
+OpReturn
+OpFunctionEnd)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+                             << text << std::endl;
+
+  LoopUnroller loop_unroller;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                        SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  SinglePassRunAndMatch<LoopUnroller>(text, true);
+}
+
 template <int factor>
 class PartialUnrollerTestPass : public Pass {
  public:
@@ -3401,6 +3580,215 @@
   SinglePassRunAndCheck<LoopUnroller>(shader, output, false);
 }
 
+TEST_F(PassClassTest, UnrollWithPhiReferencesPhi) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %color
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+               OpName %main "main"
+               OpName %color "color"
+               OpDecorate %color Location 0
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+     %uint_1 = OpConstant %uint 1
+     %uint_3 = OpConstant %uint 3
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+       %bool = OpTypeBool
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %11
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpPhi %float %float_0 %15 %18 %19
+         %18 = OpPhi %float %float_1 %15 %20 %19
+         %21 = OpPhi %uint %uint_1 %15 %22 %19
+         %23 = OpULessThanEqual %bool %21 %uint_3
+               OpLoopMerge %24 %19 Unroll
+               OpBranchConditional %23 %25 %24
+         %25 = OpLabel
+
+; First loop iteration
+; CHECK: [[next_phi1_0:%\w+]] = OpFSub %float %float_1 %float_0
+
+; Second loop iteration
+; CHECK: [[next_phi1_1:%\w+]] = OpFSub %float [[next_phi1_0]] %float_1
+
+; Third loop iteration
+; CHECK:                        OpFSub %float [[next_phi1_1]] [[next_phi1_0]]
+
+         %20 = OpFSub %float %18 %17
+               OpBranch %19
+         %19 = OpLabel
+         %22 = OpIAdd %uint %21 %uint_1
+               OpBranch %16
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+                             << text << std::endl;
+
+  LoopUnroller loop_unroller;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                        SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  SinglePassRunAndMatch<LoopUnroller>(text, true);
+}
+
+TEST_F(PassClassTest, UnrollWithDoublePhiReferencesPhi) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %color
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+               OpName %main "main"
+               OpName %color "color"
+               OpDecorate %color Location 0
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+     %uint_1 = OpConstant %uint 1
+     %uint_3 = OpConstant %uint 3
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+       %bool = OpTypeBool
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %11
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpPhi %float %float_1 %15 %18 %19
+         %18 = OpPhi %float %float_0 %15 %20 %19
+         %20 = OpPhi %float %float_1 %15 %21 %19
+         %22 = OpPhi %uint %uint_1 %15 %23 %19
+         %24 = OpULessThanEqual %bool %22 %uint_3
+               OpLoopMerge %25 %19 Unroll
+               OpBranchConditional %24 %26 %25
+         %26 = OpLabel
+
+; First loop iteration
+; CHECK: [[next_phi1_0:%\w+]] = OpFSub %float %float_1 %float_0
+; CHECK:                        OpFMul %float %float_1
+
+; Second loop iteration
+; CHECK: [[next_phi1_1:%\w+]] = OpFSub %float [[next_phi1_0]] %float_1
+; CHECK:                        OpFMul %float %float_0
+
+; Third loop iteration
+; CHECK:                        OpFSub %float [[next_phi1_1]] [[next_phi1_0]]
+; CHECK:                        OpFMul %float %float_1
+
+         %21 = OpFSub %float %20 %18
+         %27 = OpFMul %float %17 %21
+               OpBranch %19
+         %19 = OpLabel
+         %23 = OpIAdd %uint %22 %uint_1
+               OpBranch %16
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+                             << text << std::endl;
+
+  LoopUnroller loop_unroller;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                        SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  SinglePassRunAndMatch<LoopUnroller>(text, true);
+}
+
+TEST_F(PassClassTest, PartialUnrollWithPhiReferencesPhi) {
+  // With LocalMultiStoreElimPass
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %color
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+               OpName %main "main"
+               OpName %color "color"
+               OpDecorate %color Location 0
+       %uint = OpTypeInt 32 0
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %float_1 = OpConstant %float 1
+     %uint_1 = OpConstant %uint 1
+     %uint_3 = OpConstant %uint 3
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+       %bool = OpTypeBool
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+       %main = OpFunction %void None %11
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpPhi %float %float_0 %15 %18 %19
+         %18 = OpPhi %float %float_1 %15 %20 %19
+         %21 = OpPhi %uint %uint_1 %15 %22 %19
+         %23 = OpULessThanEqual %bool %21 %uint_3
+               OpLoopMerge %24 %19 Unroll
+               OpBranchConditional %23 %25 %24
+         %25 = OpLabel
+
+; CHECK: [[phi0_0:%\w+]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[phi1_0:%\w+]]
+; CHECK:      [[phi1_0]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[sub:%\w+]]
+
+; CHECK: [[sub]] = OpFSub {{%\w+}} [[phi1_0]] [[phi0_0]]
+
+; CHECK: [[phi0_1:%\w+]] = OpPhi {{%\w+}} [[phi0_0]]
+; CHECK: [[phi1_1:%\w+]] = OpPhi {{%\w+}} [[phi1_0]]
+
+; CHECK: [[sub:%\w+]] = OpFSub {{%\w+}} [[phi1_1]] [[phi0_1]]
+
+; CHECK: OpFSub {{%\w+}} [[sub]] [[phi1_1]]
+
+         %20 = OpFSub %float %18 %17
+               OpBranch %19
+         %19 = OpLabel
+         %22 = OpIAdd %uint %21 %uint_1
+               OpBranch %16
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+                             << text << std::endl;
+
+  LoopUnroller loop_unroller;
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  SinglePassRunAndMatch<PartialUnrollerTestPass<2>>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index fd97efa..21960d1 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -2567,6 +2567,39 @@
   SinglePassRunAndMatch<MergeReturnPass>(before, true);
 }
 
+TEST_F(MergeReturnPassTest, OverflowTest1) {
+  const std::string text =
+      R"(
+; CHECK: OpReturn
+; CHECK-NOT: OpReturn
+; CHECK: OpFunctionEnd
+               OpCapability ClipDistance
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+          %2 = OpFunction %void None %6
+    %4194303 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpLoopMerge %19 %20 None
+               OpBranch %21
+         %21 = OpLabel
+               OpReturn
+         %20 = OpLabel
+               OpBranch %18
+         %19 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto result =
+      SinglePassRunToBinary<MergeReturnPass>(text, /* skip_nop = */ true);
+  EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp
index 7672e8f..abb5cde 100644
--- a/test/opt/reduce_load_size_test.cpp
+++ b/test/opt/reduce_load_size_test.cpp
@@ -17,6 +17,12 @@
 #include "test/opt/pass_fixture.h"
 #include "test/opt/pass_utils.h"
 
+namespace {
+
+const double kDefaultLoadReductionThreshold = 0.9;
+
+}  // namespace
+
 namespace spvtools {
 namespace opt {
 namespace {
@@ -104,7 +110,8 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
-  SinglePassRunAndMatch<ReduceLoadSize>(test, false);
+  SinglePassRunAndMatch<ReduceLoadSize>(test, false,
+                                        kDefaultLoadReductionThreshold);
 }
 
 TEST_F(ReduceLoadSizeTest, cbuffer_load_extract_not_affected_by_debug_instr) {
@@ -202,7 +209,8 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
-  SinglePassRunAndMatch<ReduceLoadSize>(test, false);
+  SinglePassRunAndMatch<ReduceLoadSize>(test, false,
+                                        kDefaultLoadReductionThreshold);
 }
 
 TEST_F(ReduceLoadSizeTest, cbuffer_load_extract_vector) {
@@ -280,7 +288,8 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
-  SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
+  SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false,
+                                        kDefaultLoadReductionThreshold);
 }
 
 TEST_F(ReduceLoadSizeTest, cbuffer_load_5_extract) {
@@ -351,7 +360,8 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
-  SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
+  SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false,
+                                        kDefaultLoadReductionThreshold);
 }
 
 TEST_F(ReduceLoadSizeTest, cbuffer_load_fully_used) {
@@ -416,7 +426,76 @@
   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
                         SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
-  SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
+  SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false,
+                                        kDefaultLoadReductionThreshold);
+}
+
+TEST_F(ReduceLoadSizeTest, replace_cbuffer_load_fully_used) {
+  const std::string test =
+      R"(
+               OpCapability Shader
+               OpCapability SampledBuffer
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %out_var_SV_Target0
+               OpExecutionMode %main OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_MaterialInstancing_cbuffer "type.MaterialInstancing_cbuffer"
+               OpMemberName %type_MaterialInstancing_cbuffer 0 "MaterialInstancing_constants"
+               OpName %MaterialInstancing_Constants "MaterialInstancing_Constants"
+               OpMemberName %MaterialInstancing_Constants 0 "offset0"
+               OpMemberName %MaterialInstancing_Constants 1 "params"
+               OpName %InstancingParams_Constants "InstancingParams_Constants"
+               OpMemberName %InstancingParams_Constants 0 "offset1"
+               OpName %MaterialInstancing_cbuffer "MaterialInstancing_cbuffer"
+               OpName %out_var_SV_Target0 "out.var.SV_Target0"
+               OpName %main "main"
+               OpDecorate %out_var_SV_Target0 Location 0
+               OpDecorate %MaterialInstancing_cbuffer DescriptorSet 6
+               OpDecorate %MaterialInstancing_cbuffer Binding 0
+               OpMemberDecorate %InstancingParams_Constants 0 Offset 0
+               OpMemberDecorate %MaterialInstancing_Constants 0 Offset 0
+               OpMemberDecorate %MaterialInstancing_Constants 1 Offset 16
+               OpMemberDecorate %type_MaterialInstancing_cbuffer 0 Offset 0
+               OpDecorate %type_MaterialInstancing_cbuffer Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %v4int = OpTypeVector %int 4
+%InstancingParams_Constants = OpTypeStruct %v4int
+%MaterialInstancing_Constants = OpTypeStruct %v4int %InstancingParams_Constants
+%type_MaterialInstancing_cbuffer = OpTypeStruct %MaterialInstancing_Constants
+%_ptr_Uniform_type_MaterialInstancing_cbuffer = OpTypePointer Uniform %type_MaterialInstancing_cbuffer
+%_ptr_Output_int = OpTypePointer Output %int
+       %void = OpTypeVoid
+         %60 = OpTypeFunction %void
+%_ptr_Uniform_MaterialInstancing_Constants = OpTypePointer Uniform %MaterialInstancing_Constants
+%MaterialInstancing_cbuffer = OpVariable %_ptr_Uniform_type_MaterialInstancing_cbuffer Uniform
+%out_var_SV_Target0 = OpVariable %_ptr_Output_int Output
+       %main = OpFunction %void None %60
+         %80 = OpLabel
+        %131 = OpAccessChain %_ptr_Uniform_MaterialInstancing_Constants %MaterialInstancing_cbuffer %int_0
+        %132 = OpLoad %MaterialInstancing_Constants %131
+; CHECK: [[ac1:%\w+]] = OpAccessChain {{%\w+}} %MaterialInstancing_cbuffer %int_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain {{%\w+}} [[ac1]] %uint_0
+; CHECK: OpLoad %v4int [[ac2]]
+
+; CHECK: [[ac3:%\w+]] = OpAccessChain {{%\w+}} [[ac1]] %uint_1
+; CHECK: [[ac4:%\w+]] = OpAccessChain {{%\w+}} [[ac3]] %uint_0
+; CHECK: OpLoad %v4int [[ac4]]
+        %134 = OpCompositeExtract %v4int %132 0
+        %135 = OpCompositeExtract %InstancingParams_Constants %132 1
+        %136 = OpCompositeExtract %v4int %135 0
+        %149 = OpCompositeExtract %int %134 0
+        %185 = OpCompositeExtract %int %136 0
+        %156 = OpIAdd %int %149 %185
+               OpStore %out_var_SV_Target0 %156
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                        SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  SinglePassRunAndMatch<ReduceLoadSize>(test, false, 1.1);
 }
 
 }  // namespace
diff --git a/test/opt/redundancy_elimination_test.cpp b/test/opt/redundancy_elimination_test.cpp
index 474f466..28eda73 100644
--- a/test/opt/redundancy_elimination_test.cpp
+++ b/test/opt/redundancy_elimination_test.cpp
@@ -335,6 +335,32 @@
   SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
 }
 
+TEST_F(RedundancyEliminationTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<RedundancyEliminationPass>(text, text, false);
+}
+
 }  // namespace
 }  // namespace opt
-}  // namespace spvtools
+}  // namespace spvtools
\ No newline at end of file
diff --git a/test/opt/relax_float_ops_test.cpp b/test/opt/relax_float_ops_test.cpp
index 14cde0b..b9cb0de 100644
--- a/test/opt/relax_float_ops_test.cpp
+++ b/test/opt/relax_float_ops_test.cpp
@@ -137,6 +137,86 @@
       true);
 }
 
+TEST_F(RelaxFloatOpsTest, RelaxFloatOpsForLinkage) {
+  const std::string defs0 =
+      R"(OpCapability Shader
+OpCapability Linkage
+OpCapability Sampled1D
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpSource HLSL 630
+OpName %main "main"
+OpName %g_tTex1df4 "g_tTex1df4"
+OpName %g_sSamp "g_sSamp"
+OpName %i_Tex0 "i.Tex0"
+OpName %i_Tex1 "i.Tex1"
+OpName %_entryPointOutput_Color "@entryPointOutput.Color"
+OpDecorate %main LinkageAttributes "main" Export
+OpDecorate %g_tTex1df4 DescriptorSet 0
+OpDecorate %g_tTex1df4 Binding 0
+OpDecorate %g_sSamp DescriptorSet 0
+OpDecorate %g_sSamp Binding 0
+OpDecorate %i_Tex0 Location 0
+OpDecorate %i_Tex1 Location 1
+OpDecorate %_entryPointOutput_Color Location 0
+)";
+
+  const std::string defs1 =
+      R"(%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%17 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
+%g_tTex1df4 = OpVariable %_ptr_UniformConstant_17 UniformConstant
+%21 = OpTypeSampler
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%g_sSamp = OpVariable %_ptr_UniformConstant_21 UniformConstant
+%25 = OpTypeSampledImage %17
+%_ptr_Input_float = OpTypePointer Input %float
+%i_Tex0 = OpVariable %_ptr_Input_float Input
+%i_Tex1 = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_Color = OpVariable %_ptr_Output_v4float Output
+%float_0_5 = OpConstant %float 0.5
+%116 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5
+)";
+
+  const std::string relax_decos =
+      R"(OpDecorate %60 RelaxedPrecision
+OpDecorate %63 RelaxedPrecision
+OpDecorate %82 RelaxedPrecision
+OpDecorate %88 RelaxedPrecision
+OpDecorate %91 RelaxedPrecision
+OpDecorate %94 RelaxedPrecision
+)";
+
+  const std::string func_orig =
+      R"(%main = OpFunction %void None %3
+%5 = OpLabel
+%60 = OpLoad %float %i_Tex0
+%63 = OpLoad %float %i_Tex1
+%77 = OpLoad %17 %g_tTex1df4
+%78 = OpLoad %21 %g_sSamp
+%79 = OpSampledImage %25 %77 %78
+%82 = OpImageSampleImplicitLod %v4float %79 %60
+%83 = OpLoad %17 %g_tTex1df4
+%84 = OpLoad %21 %g_sSamp
+%85 = OpSampledImage %25 %83 %84
+%88 = OpImageSampleImplicitLod %v4float %85 %63
+%91 = OpFAdd %v4float %82 %88
+%94 = OpFMul %v4float %91 %116
+OpStore %_entryPointOutput_Color %94
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<RelaxFloatOpsPass>(
+      defs0 + defs1 + func_orig, defs0 + relax_decos + defs1 + func_orig, true,
+      true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/remove_unused_interface_variables_test.cpp b/test/opt/remove_unused_interface_variables_test.cpp
new file mode 100644
index 0000000..ddf027f
--- /dev/null
+++ b/test/opt/remove_unused_interface_variables_test.cpp
@@ -0,0 +1,185 @@
+// Copyright (c) 2021 ZHOU He
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using RemoveUnusedInterfaceVariablesTest = PassTest<::testing::Test>;
+
+static const std::string expected = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %_Z5func1v "_Z5func1v" %out_var_SV_TARGET
+OpEntryPoint Fragment %_Z5func2v "_Z5func2v" %out_var_SV_TARGET_0
+OpExecutionMode %_Z5func1v OriginUpperLeft
+OpExecutionMode %_Z5func2v OriginUpperLeft
+OpSource HLSL 630
+OpName %type_cba "type.cba"
+OpMemberName %type_cba 0 "color"
+OpName %cba "cba"
+OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+OpName %out_var_SV_TARGET_0 "out.var.SV_TARGET"
+OpName %_Z5func1v "_Z5func1v"
+OpName %_Z5func2v "_Z5func2v"
+OpDecorate %out_var_SV_TARGET Location 0
+OpDecorate %out_var_SV_TARGET_0 Location 0
+OpDecorate %cba DescriptorSet 0
+OpDecorate %cba Binding 0
+OpMemberDecorate %type_cba 0 Offset 0
+OpDecorate %type_cba Block
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%type_cba = OpTypeStruct %v4float
+%_ptr_Uniform_type_cba = OpTypePointer Uniform %type_cba
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%14 = OpTypeFunction %void
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%cba = OpVariable %_ptr_Uniform_type_cba Uniform
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+%out_var_SV_TARGET_0 = OpVariable %_ptr_Output_v4float Output
+%_Z5func1v = OpFunction %void None %14
+%16 = OpLabel
+%17 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
+%18 = OpLoad %v4float %17
+OpStore %out_var_SV_TARGET %18
+OpReturn
+OpFunctionEnd
+%_Z5func2v = OpFunction %void None %14
+%19 = OpLabel
+%20 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
+%21 = OpLoad %v4float %20
+OpStore %out_var_SV_TARGET_0 %21
+OpReturn
+OpFunctionEnd
+)";
+
+TEST_F(RemoveUnusedInterfaceVariablesTest, RemoveUnusedVariable) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %_Z5func1v "_Z5func1v" %out_var_SV_TARGET %out_var_SV_TARGET_0
+OpEntryPoint Fragment %_Z5func2v "_Z5func2v" %out_var_SV_TARGET %out_var_SV_TARGET_0
+OpExecutionMode %_Z5func1v OriginUpperLeft
+OpExecutionMode %_Z5func2v OriginUpperLeft
+OpSource HLSL 630
+OpName %type_cba "type.cba"
+OpMemberName %type_cba 0 "color"
+OpName %cba "cba"
+OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+OpName %out_var_SV_TARGET_0 "out.var.SV_TARGET"
+OpName %_Z5func1v "_Z5func1v"
+OpName %_Z5func2v "_Z5func2v"
+OpDecorate %out_var_SV_TARGET Location 0
+OpDecorate %out_var_SV_TARGET_0 Location 0
+OpDecorate %cba DescriptorSet 0
+OpDecorate %cba Binding 0
+OpMemberDecorate %type_cba 0 Offset 0
+OpDecorate %type_cba Block
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%type_cba = OpTypeStruct %v4float
+%_ptr_Uniform_type_cba = OpTypePointer Uniform %type_cba
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%14 = OpTypeFunction %void
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%cba = OpVariable %_ptr_Uniform_type_cba Uniform
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+%out_var_SV_TARGET_0 = OpVariable %_ptr_Output_v4float Output
+%_Z5func1v = OpFunction %void None %14
+%16 = OpLabel
+%17 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
+%18 = OpLoad %v4float %17
+OpStore %out_var_SV_TARGET %18
+OpReturn
+OpFunctionEnd
+%_Z5func2v = OpFunction %void None %14
+%19 = OpLabel
+%20 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
+%21 = OpLoad %v4float %20
+OpStore %out_var_SV_TARGET_0 %21
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<RemoveUnusedInterfaceVariablesPass>(text, expected,
+                                                            true, true);
+}
+
+TEST_F(RemoveUnusedInterfaceVariablesTest, FixMissingVariable) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %_Z5func1v "_Z5func1v"
+OpEntryPoint Fragment %_Z5func2v "_Z5func2v"
+OpExecutionMode %_Z5func1v OriginUpperLeft
+OpExecutionMode %_Z5func2v OriginUpperLeft
+OpSource HLSL 630
+OpName %type_cba "type.cba"
+OpMemberName %type_cba 0 "color"
+OpName %cba "cba"
+OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+OpName %out_var_SV_TARGET_0 "out.var.SV_TARGET"
+OpName %_Z5func1v "_Z5func1v"
+OpName %_Z5func2v "_Z5func2v"
+OpDecorate %out_var_SV_TARGET Location 0
+OpDecorate %out_var_SV_TARGET_0 Location 0
+OpDecorate %cba DescriptorSet 0
+OpDecorate %cba Binding 0
+OpMemberDecorate %type_cba 0 Offset 0
+OpDecorate %type_cba Block
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%type_cba = OpTypeStruct %v4float
+%_ptr_Uniform_type_cba = OpTypePointer Uniform %type_cba
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%void = OpTypeVoid
+%14 = OpTypeFunction %void
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%cba = OpVariable %_ptr_Uniform_type_cba Uniform
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+%out_var_SV_TARGET_0 = OpVariable %_ptr_Output_v4float Output
+%_Z5func1v = OpFunction %void None %14
+%16 = OpLabel
+%17 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
+%18 = OpLoad %v4float %17
+OpStore %out_var_SV_TARGET %18
+OpReturn
+OpFunctionEnd
+%_Z5func2v = OpFunction %void None %14
+%19 = OpLabel
+%20 = OpAccessChain %_ptr_Uniform_v4float %cba %int_0
+%21 = OpLoad %v4float %20
+OpStore %out_var_SV_TARGET_0 %21
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<RemoveUnusedInterfaceVariablesPass>(text, expected,
+                                                            true, true);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/replace_desc_array_access_using_var_index_test.cpp b/test/opt/replace_desc_array_access_using_var_index_test.cpp
new file mode 100644
index 0000000..ca62581
--- /dev/null
+++ b/test/opt/replace_desc_array_access_using_var_index_test.cpp
@@ -0,0 +1,411 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <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 ReplaceDescArrayAccessUsingVarIndexTest = PassTest<::testing::Test>;
+
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest,
+       ReplaceAccessChainToTextureArray) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET
+               OpExecutionMode %psmain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_sampler "type.sampler"
+               OpName %Sampler0 "Sampler0"
+               OpName %type_2d_image "type.2d.image"
+               OpName %Tex0 "Tex0"
+               OpName %in_var_INSTANCEID "in.var.INSTANCEID"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %psmain "psmain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorate %in_var_INSTANCEID Flat
+               OpDecorate %in_var_INSTANCEID Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %Sampler0 DescriptorSet 0
+               OpDecorate %Sampler0 Binding 1
+               OpDecorate %Tex0 DescriptorSet 0
+               OpDecorate %Tex0 Binding 2
+       %bool = OpTypeBool
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+       %uint = OpTypeInt 32 0
+     %uint_3 = OpConstant %uint 3
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown
+%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3
+%_ptr_UniformConstant__arr_type_2d_image_uint_3 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_3
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Input_uint = OpTypePointer Input %uint
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+    %v2float = OpTypeVector %float 2
+     %v2uint = OpTypeVector %uint 2
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+         %27 = OpConstantComposite %v2uint %uint_0 %uint_1
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %Sampler0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+       %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_3 UniformConstant
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+     %uint_2 = OpConstant %uint 2
+         %66 = OpConstantNull %v4float
+
+; CHECK: [[null_value:%\w+]] = OpConstantNull %v4float
+
+     %psmain = OpFunction %void None %21
+         %39 = OpLabel
+         %29 = OpLoad %v4float %gl_FragCoord
+         %30 = OpLoad %uint %in_var_INSTANCEID
+         %37 = OpIEqual %bool %30 %uint_2
+               OpSelectionMerge %38 None
+               OpBranchConditional %37 %28 %40
+
+; CHECK: [[var_index:%\w+]] = OpLoad %uint %in_var_INSTANCEID
+; CHECK: OpSelectionMerge [[cond_branch_merge:%\w+]] None
+; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb_cond_br:%\w+]]
+
+         %28 = OpLabel
+         %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30
+         %32 = OpLoad %type_2d_image %31
+               OpImageWrite %32 %27 %29
+
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]] 2 [[case2:%\w+]]
+; CHECK: [[case0]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case1]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case2]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[default]] = OpLabel
+; CHECK: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+
+         %33 = OpLoad %type_sampler %Sampler0
+         %34 = OpVectorShuffle %v2float %29 %29 0 1
+         %35 = OpSampledImage %type_sampled_image %32 %33
+         %36 = OpImageSampleImplicitLod %v4float %35 %34 None
+
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]] 2 [[case2:%\w+]]
+; CHECK: [[case0]] = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0
+; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]]
+; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]]
+; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]]
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case1]] = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1
+; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0
+; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]]
+; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]]
+; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]]
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case2]] = OpLabel
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_2
+; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0
+; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]]
+; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]]
+; CHECK: [[value2:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]]
+; CHECK: OpBranch [[merge]]
+; CHECK: [[default]] = OpLabel
+; CHECK: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK: [[phi0:%\w+]] = OpPhi %v4float [[value0]] [[case0]] [[value1]] [[case1]] [[value2]] [[case2]] [[null_value]] [[default]]
+
+               OpBranch %38
+         %40 = OpLabel
+               OpBranch %38
+         %38 = OpLabel
+         %41 = OpPhi %v4float %36 %28 %29 %40
+
+; CHECK: OpBranch [[cond_branch_merge]]
+; CHECK: [[bb_cond_br]] = OpLabel
+; CHECK: OpBranch [[cond_branch_merge]]
+; CHECK: [[cond_branch_merge]] = OpLabel
+; CHECK: [[phi1:%\w+]] = OpPhi %v4float [[phi0]] [[merge]] {{%\w+}} [[bb_cond_br]]
+; CHECK: OpStore {{%\w+}} [[phi1]]
+
+               OpStore %out_var_SV_TARGET %41
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest,
+       ReplaceAccessChainToTextureArrayAndSamplerArray) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET
+               OpExecutionMode %psmain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_sampler "type.sampler"
+               OpName %Sampler0 "Sampler0"
+               OpName %type_2d_image "type.2d.image"
+               OpName %Tex0 "Tex0"
+               OpName %in_var_INSTANCEID "in.var.INSTANCEID"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %psmain "psmain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorate %in_var_INSTANCEID Flat
+               OpDecorate %in_var_INSTANCEID Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %Sampler0 DescriptorSet 0
+               OpDecorate %Sampler0 Binding 1
+               OpDecorate %Tex0 DescriptorSet 0
+               OpDecorate %Tex0 Binding 2
+%type_sampler = OpTypeSampler
+       %uint = OpTypeInt 32 0
+     %uint_2 = OpConstant %uint 2
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+%_arr_type_sampler_uint_2 = OpTypeArray %type_sampler %uint_2
+%_ptr_UniformConstant__arr_type_sampler_uint_2 = OpTypePointer UniformConstant %_arr_type_sampler_uint_2
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown
+%_arr_type_2d_image_uint_2 = OpTypeArray %type_2d_image %uint_2
+%_ptr_UniformConstant__arr_type_2d_image_uint_2 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_2
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Input_uint = OpTypePointer Input %uint
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+    %v2float = OpTypeVector %float 2
+     %v2uint = OpTypeVector %uint 2
+     %uint_0 = OpConstant %uint 0
+     %uint_1 = OpConstant %uint 1
+         %27 = OpConstantComposite %v2uint %uint_0 %uint_1
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %Sampler0 = OpVariable %_ptr_UniformConstant__arr_type_sampler_uint_2 UniformConstant
+       %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_2 UniformConstant
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+         %66 = OpConstantNull %v4float
+     %psmain = OpFunction %void None %21
+         %28 = OpLabel
+         %29 = OpLoad %v4float %gl_FragCoord
+         %30 = OpLoad %uint %in_var_INSTANCEID
+         %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30
+         %32 = OpLoad %type_2d_image %31
+               OpImageWrite %32 %27 %29
+
+; CHECK: [[null_value:%\w+]] = OpConstantNull %v4float
+
+; CHECK: [[var_index:%\w+]] = OpLoad %uint %in_var_INSTANCEID
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]]
+; CHECK: [[case0]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[case1]] = OpLabel
+; CHECK: OpAccessChain
+; CHECK: OpLoad
+; CHECK: OpImageWrite
+; CHECK: OpBranch [[merge]]
+; CHECK: [[default]] = OpLabel
+; CHECK: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+
+         %33 = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %30
+         %37 = OpLoad %type_sampler %33
+         %34 = OpVectorShuffle %v2float %29 %29 0 1
+         %35 = OpSampledImage %type_sampled_image %32 %37
+         %36 = OpImageSampleImplicitLod %v4float %35 %34 None
+
+; SPIR-V instructions to be replaced (will be killed by ADCE)
+; CHECK: OpSelectionMerge
+; CHECK: OpSwitch
+
+; CHECK: OpSelectionMerge [[merge_sampler:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default_sampler:%\w+]] 0 [[case_sampler0:%\w+]] 1 [[case_sampler1:%\w+]]
+
+; CHECK: [[case_sampler0]] = OpLabel
+; CHECK: OpSelectionMerge [[merge_texture0:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default_texture:%\w+]] 0 [[case_texture0:%\w+]] 1 [[case_texture1:%\w+]]
+; CHECK: [[case_texture0]] = OpLabel
+; CHECK: [[pt0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK: [[ps0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_0
+; CHECK: [[s0:%\w+]] = OpLoad %type_sampler [[ps0]]
+; CHECK: [[t0:%\w+]] = OpLoad %type_2d_image [[pt0]]
+; CHECK: [[sampledImg0:%\w+]] = OpSampledImage %type_sampled_image [[t0]] [[s0]]
+; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg0]]
+; CHECK: OpBranch [[merge_texture0]]
+; CHECK: [[case_texture1]] = OpLabel
+; CHECK: [[pt1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1
+; CHECK: [[ps0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_0
+; CHECK: [[s0:%\w+]] = OpLoad %type_sampler [[ps0]]
+; CHECK: [[t1:%\w+]] = OpLoad %type_2d_image [[pt1]]
+; CHECK: [[sampledImg1:%\w+]] = OpSampledImage %type_sampled_image [[t1]] [[s0]]
+; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg1]]
+; CHECK: OpBranch [[merge_texture0]]
+; CHECK: [[default_texture]] = OpLabel
+; CHECK: OpBranch [[merge_texture0]]
+; CHECK: [[merge_texture0]] = OpLabel
+; CHECK: [[phi0:%\w+]] = OpPhi %v4float [[value0]] [[case_texture0]] [[value1]] [[case_texture1]] [[null_value]] [[default_texture]]
+; CHECK: OpBranch [[merge_sampler]]
+
+; CHECK: [[case_sampler1]] = OpLabel
+; CHECK: OpSelectionMerge [[merge_texture1:%\w+]] None
+; CHECK: OpSwitch [[var_index]] [[default_texture:%\w+]] 0 [[case_texture0:%\w+]] 1 [[case_texture1:%\w+]]
+; CHECK: [[case_texture0]] = OpLabel
+; CHECK: [[pt0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK: [[ps1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_1
+; CHECK: [[s1:%\w+]] = OpLoad %type_sampler [[ps1]]
+; CHECK: [[t0:%\w+]] = OpLoad %type_2d_image [[pt0]]
+; CHECK: [[sampledImg0:%\w+]] = OpSampledImage %type_sampled_image [[t0]] [[s1]]
+; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg0]]
+; CHECK: OpBranch [[merge_texture1]]
+; CHECK: [[case_texture1]] = OpLabel
+; CHECK: [[pt1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1
+; CHECK: [[ps1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_1
+; CHECK: [[s1:%\w+]] = OpLoad %type_sampler [[ps1]]
+; CHECK: [[t1:%\w+]] = OpLoad %type_2d_image [[pt1]]
+; CHECK: [[sampledImg1:%\w+]] = OpSampledImage %type_sampled_image [[t1]] [[s1]]
+; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg1]]
+; CHECK: OpBranch [[merge_texture1]]
+; CHECK: [[default_texture]] = OpLabel
+; CHECK: OpBranch [[merge_texture1]]
+; CHECK: [[merge_texture1]] = OpLabel
+; CHECK: [[phi1:%\w+]] = OpPhi %v4float [[value0]] [[case_texture0]] [[value1]] [[case_texture1]] [[null_value]] [[default_texture]]
+
+; CHECK: [[default_sampler]] = OpLabel
+; CHECK: OpBranch [[merge_sampler]]
+; CHECK: [[merge_sampler]] = OpLabel
+; CHECK: OpPhi %v4float [[phi0]] [[merge_texture0]] [[phi1]] [[merge_texture1]] [[null_value]] [[default_sampler]]
+; CHECK: OpStore
+
+               OpStore %out_var_SV_TARGET %36
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
+TEST_F(ReplaceDescArrayAccessUsingVarIndexTest,
+       ReplaceAccessChainToTextureArrayWithSingleElement) {
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET
+               OpExecutionMode %psmain OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_sampler "type.sampler"
+               OpName %Sampler0 "Sampler0"
+               OpName %type_2d_image "type.2d.image"
+               OpName %Tex0 "Tex0"
+               OpName %in_var_INSTANCEID "in.var.INSTANCEID"
+               OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+               OpName %psmain "psmain"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %gl_FragCoord BuiltIn FragCoord
+               OpDecorate %in_var_INSTANCEID Flat
+               OpDecorate %in_var_INSTANCEID Location 0
+               OpDecorate %out_var_SV_TARGET Location 0
+               OpDecorate %Sampler0 DescriptorSet 0
+               OpDecorate %Sampler0 Binding 1
+               OpDecorate %Tex0 DescriptorSet 0
+               OpDecorate %Tex0 Binding 2
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+      %float = OpTypeFloat 32
+%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown
+%_arr_type_2d_image_uint_1 = OpTypeArray %type_2d_image %uint_1
+%_ptr_UniformConstant__arr_type_2d_image_uint_1 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_1
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Input_uint = OpTypePointer Input %uint
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+    %v2float = OpTypeVector %float 2
+     %v2uint = OpTypeVector %uint 2
+     %uint_0 = OpConstant %uint 0
+         %27 = OpConstantComposite %v2uint %uint_0 %uint_1
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %Sampler0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+       %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_1 UniformConstant
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+     %uint_2 = OpConstant %uint 2
+         %66 = OpConstantNull %v4float
+     %psmain = OpFunction %void None %21
+         %28 = OpLabel
+         %29 = OpLoad %v4float %gl_FragCoord
+         %30 = OpLoad %uint %in_var_INSTANCEID
+         %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30
+         %32 = OpLoad %type_2d_image %31
+               OpImageWrite %32 %27 %29
+
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0
+; CHECK-NOT: OpAccessChain
+; CHECK-NOT: OpSwitch
+; CHECK-NOT: OpPhi
+
+         %33 = OpLoad %type_sampler %Sampler0
+         %34 = OpVectorShuffle %v2float %29 %29 0 1
+         %35 = OpSampledImage %type_sampled_image %32 %33
+         %36 = OpImageSampleImplicitLod %v4float %35 %34 None
+
+               OpStore %out_var_SV_TARGET %36
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true);
+}
+
+}  // namespace
+}  // namespace opt
+}  // namespace spvtools
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index 1073855..8cb888c 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -1935,12 +1935,12 @@
 ; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable
 ; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]]
 ; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function
-; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_3
 ; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function
-; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2
 ; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function
-; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1
 ; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_3
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1
 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0
 ; CHECK-NOT: DebugDeclare
 %decl = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr
@@ -2058,10 +2058,10 @@
 ; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_float Function %float_1
 ; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32
 ; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_float Function %float_1
+; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32
 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_2
 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 %int_0
 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_1 %int_1
-; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32
 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0
 ; CHECK-NOT: DebugDeclare
 %decl = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr
@@ -2174,12 +2174,12 @@
 
 ; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugLocalVariable
 ; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function
-; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr:%\w+]] %int_3
 ; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function
-; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2
 ; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function
-; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1
 ; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr:%\w+]] %int_3
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1
 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0
 
 OpBranch %20
@@ -2197,6 +2197,72 @@
   SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
 }
 
+TEST_F(ScalarReplacementTest, ImageTexelPointer) {
+  // Test whether the scalar replacement correctly checks the
+  // OpImageTexelPointer user of an aggregate with an image type.
+  const std::string text = R"(
+;
+; CHECK: [[imgTy:%\w+]] = OpTypeImage %uint Buffer 2 0 0 2 R32ui
+; CHECK: [[ptrImgTy:%\w+]] = OpTypePointer Function [[imgTy]]
+; CHECK: [[img:%\w+]] = OpVariable [[ptrImgTy]] Function
+; CHECK: [[imgTexelPtr:%\w+]] = OpImageTexelPointer {{%\w+}} [[img]] %uint_0 %uint_0
+; CHECK: OpAtomicIAdd %uint [[imgTexelPtr]] %uint_1 %uint_0 %uint_1
+;
+OpCapability Shader
+OpCapability SampledBuffer
+OpCapability ImageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpExecutionMode %1 LocalSize 64 1 1
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Image_uint = OpTypePointer Image %uint
+%type_buffer_image = OpTypeImage %uint Buffer 2 0 0 2 R32ui
+%_ptr_Function_type_buffer_image = OpTypePointer Function %type_buffer_image
+%image_struct = OpTypeStruct %type_buffer_image %type_buffer_image
+%_ptr_Function_image_struct = OpTypePointer Function %image_struct
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%3 = OpVariable %_ptr_Function_image_struct Function
+%4 = OpAccessChain %_ptr_Function_type_buffer_image %3 %uint_1
+%5 = OpImageTexelPointer %_ptr_Image_uint %4 %uint_0 %uint_0
+%6 = OpAtomicIAdd %uint %5 %uint_1 %uint_0 %uint_1
+OpReturn
+OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<ScalarReplacementPass>(text, false);
+}
+
+TEST_F(ScalarReplacementTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<ScalarReplacementPass>(text, text, false);
+}
+
 }  // 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 5e63862..f1dd50e 100644
--- a/test/opt/set_spec_const_default_value_test.cpp
+++ b/test/opt/set_spec_const_default_value_test.cpp
@@ -935,6 +935,98 @@
             "%2 = OpSpecConstantTrue %bool\n"
             "%3 = OpSpecConstantTrue %bool\n",
         },
+        // 19. 16-bit signed int type.
+        {
+            // code
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "%short = OpTypeInt 16 1\n"
+            "%1 = OpSpecConstant %short 10\n"
+            "%2 = OpSpecConstant %short 11\n"
+            "%3 = OpSpecConstant %short 11\n",
+            // default values
+            SpecIdToValueBitPatternMap{
+                {100, {32767}}, {101, {0xffff}}, {102, {0xffffffd6}}},
+            // expected. These are sign-extended
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "%short = OpTypeInt 16 1\n"
+            "%1 = OpSpecConstant %short 32767\n"
+            "%2 = OpSpecConstant %short -1\n"
+            "%3 = OpSpecConstant %short -42\n",
+        },
+        // 20. 16-bit unsigned int type.
+        {
+            // code
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "%ushort = OpTypeInt 16 0\n"
+            "%1 = OpSpecConstant %ushort 10\n"
+            "%2 = OpSpecConstant %ushort 11\n"
+            "%3 = OpSpecConstant %ushort 11\n",
+            // default values
+            SpecIdToValueBitPatternMap{
+                {100, {32767}}, {101, {0xffff}}, {102, {0xffffffd6}}},
+            // expected. Upper bits are always zero.
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "%ushort = OpTypeInt 16 0\n"
+            "%1 = OpSpecConstant %ushort 32767\n"
+            "%2 = OpSpecConstant %ushort 65535\n"
+            "%3 = OpSpecConstant %ushort 65494\n",
+        },
+        // 21. 8-bit signed int type.
+        {
+            // code
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "%char = OpTypeInt 8 1\n"
+            "%1 = OpSpecConstant %char 10\n"
+            "%2 = OpSpecConstant %char 11\n"
+            "%3 = OpSpecConstant %char 11\n",
+            // default values
+            SpecIdToValueBitPatternMap{
+                {100, {127}}, {101, {128}}, {102, {0xd6}}},
+            // expected. These are sign extended
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "%char = OpTypeInt 8 1\n"
+            "%1 = OpSpecConstant %char 127\n"
+            "%2 = OpSpecConstant %char -128\n"
+            "%3 = OpSpecConstant %char -42\n",
+        },
+        // 22. 8-bit unsigned int type.
+        {
+            // code
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "OpDecorate %4 SpecId 103\n"
+            "%uchar = OpTypeInt 8 0\n"
+            "%1 = OpSpecConstant %uchar 10\n"
+            "%2 = OpSpecConstant %uchar 11\n"
+            "%3 = OpSpecConstant %uchar 11\n"
+            "%4 = OpSpecConstant %uchar 11\n",
+            // default values
+            SpecIdToValueBitPatternMap{
+                {100, {127}}, {101, {128}}, {102, {256}}, {103, {0xffffffd6}}},
+            // expected. Upper bits are always zero.
+            "OpDecorate %1 SpecId 100\n"
+            "OpDecorate %2 SpecId 101\n"
+            "OpDecorate %3 SpecId 102\n"
+            "OpDecorate %4 SpecId 103\n"
+            "%uchar = OpTypeInt 8 0\n"
+            "%1 = OpSpecConstant %uchar 127\n"
+            "%2 = OpSpecConstant %uchar 128\n"
+            "%3 = OpSpecConstant %uchar 0\n"
+            "%4 = OpSpecConstant %uchar 214\n",
+        },
     }));
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/test/opt/simplification_test.cpp b/test/opt/simplification_test.cpp
index 7a9696e..7727f56 100644
--- a/test/opt/simplification_test.cpp
+++ b/test/opt/simplification_test.cpp
@@ -360,6 +360,31 @@
   SinglePassRunAndMatch<SimplificationPass>(spirv, true);
 }
 
+TEST_F(SimplificationTest, FunctionDeclaration) {
+  // Make sure the pass works with a function declaration that is called.
+  const std::string text = R"(OpCapability Addresses
+OpCapability Linkage
+OpCapability Kernel
+OpCapability Int8
+%1 = OpExtInstImport "OpenCL.std"
+OpMemoryModel Physical64 OpenCL
+OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
+OpExecutionMode %2 ContractionOff
+OpSource Unknown 0
+OpDecorate %3 LinkageAttributes "julia_error_7712" Import
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%3 = OpFunction %void None %5
+OpFunctionEnd
+%2 = OpFunction %void None %5
+%6 = OpLabel
+%7 = OpFunctionCall %void %3
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<SimplificationPass>(text, text, false);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/upgrade_memory_model_test.cpp b/test/opt/upgrade_memory_model_test.cpp
index 7f64ffd..2cd3c7d 100644
--- a/test/opt/upgrade_memory_model_test.cpp
+++ b/test/opt/upgrade_memory_model_test.cpp
@@ -404,7 +404,7 @@
 OpExtension "SPV_KHR_variable_pointers"
 OpMemoryModel Logical GLSL450
 OpDecorate %param Coherent
-OpDecorate %param ArrayStride 4
+OpDecorate %ptr_int_StorageBuffer ArrayStride 4
 %void = OpTypeVoid
 %bool = OpTypeBool
 %int = OpTypeInt 32 0
diff --git a/test/opt/vector_dce_test.cpp b/test/opt/vector_dce_test.cpp
index 9bdad37..b14e225 100644
--- a/test/opt/vector_dce_test.cpp
+++ b/test/opt/vector_dce_test.cpp
@@ -1351,6 +1351,72 @@
   SinglePassRunAndMatch<VectorDCE>(text, true);
 }
 
+TEST_F(VectorDCETest, OutOfBoundsExtract) {
+  // It tests that the vector DCE pass is able to handle an extract with an
+  // index that is out of bounds.
+  const std::string text = R"(
+; CHECK: [[undef:%\w+]] = OpUndef %v4float
+; CHECK: OpCompositeExtract %float [[undef]] 8
+                     OpCapability Shader
+                     OpMemoryModel Logical GLSL450
+                     OpEntryPoint Fragment %main "main" %OutColor
+                     OpExecutionMode %main OriginUpperLeft
+                     OpDecorate %OutColor Location 0
+             %void = OpTypeVoid
+               %10 = OpTypeFunction %void
+            %float = OpTypeFloat 32
+          %v4float = OpTypeVector %float 4
+%_ptr_Output_float = OpTypePointer Output %float
+         %OutColor = OpVariable %_ptr_Output_float Output
+             %null = OpConstantNull %v4float
+          %float_1 = OpConstant %float 1
+             %main = OpFunction %void None %10
+               %28 = OpLabel
+               %33 = OpCompositeInsert %v4float %float_1 %null 1
+          %extract = OpCompositeExtract %float %33 8
+                     OpStore %OutColor %extract
+                     OpReturn
+                     OpFunctionEnd
+)";
+
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                        SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  SinglePassRunAndMatch<VectorDCE>(text, false);
+}
+
+TEST_F(VectorDCETest, OutOfBoundsShuffle) {
+  // It tests that the vector DCE pass is able to handle a shuffle with an
+  // index that is out of bounds.
+  const std::string text = R"(
+; CHECK: [[undef:%\w+]] = OpUndef %v4float
+; CHECK: OpVectorShuffle %v4float [[undef]] [[undef]] 9 10 11 12
+                     OpCapability Shader
+                     OpMemoryModel Logical GLSL450
+                     OpEntryPoint Fragment %main "main" %OutColor
+                     OpExecutionMode %main OriginUpperLeft
+                     OpDecorate %OutColor Location 0
+             %void = OpTypeVoid
+               %10 = OpTypeFunction %void
+            %float = OpTypeFloat 32
+          %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %OutColor = OpVariable %_ptr_Output_v4float Output
+             %null = OpConstantNull %v4float
+          %float_1 = OpConstant %float 1
+             %main = OpFunction %void None %10
+               %28 = OpLabel
+               %33 = OpCompositeInsert %v4float %float_1 %null 1
+          %shuffle = OpVectorShuffle %v4float %33 %33 9 10 11 12
+                     OpStore %OutColor %shuffle
+                     OpReturn
+                     OpFunctionEnd
+)";
+
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+                        SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+  SinglePassRunAndMatch<VectorDCE>(text, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt
index 652f0ab..121cd4f 100644
--- a/test/reduce/CMakeLists.txt
+++ b/test/reduce/CMakeLists.txt
@@ -14,6 +14,7 @@
 
 add_spvtools_unittest(TARGET reduce
         SRCS
+        conditional_branch_to_simple_conditional_branch_test.cpp
         merge_blocks_test.cpp
         operand_to_constant_test.cpp
         operand_to_undef_test.cpp
@@ -26,10 +27,10 @@
         remove_selection_test.cpp
         remove_unused_instruction_test.cpp
         remove_unused_struct_member_test.cpp
+        simple_conditional_branch_to_branch_test.cpp
+        structured_construct_to_block_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/merge_blocks_test.cpp b/test/reduce/merge_blocks_test.cpp
index 8506ee0..c472301 100644
--- a/test/reduce/merge_blocks_test.cpp
+++ b/test/reduce/merge_blocks_test.cpp
@@ -647,6 +647,64 @@
   MergeBlocksReductionPassTest_LoopReturn_Helper(true);
 }
 
+TEST(MergeBlocksReductionPassTest, MergeUnreachable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %11 = OpTypeBool
+         %12 = OpConstantFalse %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+          %9 = OpLabel
+               OpBranch %100
+        %100 = 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(), 0);
+  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 320
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %11 = OpTypeBool
+         %12 = OpConstantFalse %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CheckEqual(env, after, context.get());
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp
index 0c23411..4271660 100644
--- a/test/reduce/reduce_test_util.cpp
+++ b/test/reduce/reduce_test_util.cpp
@@ -21,6 +21,29 @@
 namespace spvtools {
 namespace reduce {
 
+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;
+  }
+};
+
 void CheckEqual(const spv_target_env env,
                 const std::vector<uint32_t>& expected_binary,
                 const std::vector<uint32_t>& actual_binary) {
@@ -55,8 +78,9 @@
 void CheckValid(spv_target_env env, const opt::IRContext* ir) {
   std::vector<uint32_t> binary;
   ir->module()->ToBinary(&binary, false);
-  SpirvTools t(env);
-  ASSERT_TRUE(t.Validate(binary));
+  SpirvTools tools(env);
+  tools.SetMessageConsumer(kConsoleMessageConsumer);
+  ASSERT_TRUE(tools.Validate(binary));
 }
 
 std::string ToString(spv_target_env env, const opt::IRContext* ir) {
diff --git a/test/reduce/structured_construct_to_block_test.cpp b/test/reduce/structured_construct_to_block_test.cpp
new file mode 100644
index 0000000..9500966
--- /dev/null
+++ b/test/reduce/structured_construct_to_block_test.cpp
@@ -0,0 +1,245 @@
+// Copyright (c) 2021 Alastair F. Donaldson
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/structured_construct_to_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(StructuredConstructToBlockReductionPassTest, SimpleTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %19 = OpConstant %6 3
+         %29 = OpConstant %6 1
+         %31 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %15
+         %15 = OpLabel
+         %18 = OpLoad %6 %8
+         %20 = OpSGreaterThan %10 %18 %19
+               OpSelectionMerge %22 None
+               OpBranchConditional %20 %21 %22
+         %21 = OpLabel
+               OpBranch %16
+         %22 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %14
+         %16 = OpLabel
+         %24 = OpLoad %6 %8
+               OpSelectionMerge %28 None
+               OpSwitch %24 %27 1 %25 2 %26
+         %27 = OpLabel
+               OpStore %8 %19
+               OpBranch %28
+         %25 = OpLabel
+               OpStore %8 %29
+               OpBranch %28
+         %26 = OpLabel
+               OpStore %8 %31
+               OpBranch %28
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredConstructToBlockReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get(), 0);
+  ASSERT_EQ(3, ops.size());
+
+  ASSERT_TRUE(ops[0]->PreconditionHolds());
+  ops[0]->TryToApply();
+  CheckValid(env, context.get());
+
+  ASSERT_TRUE(ops[1]->PreconditionHolds());
+  ops[1]->TryToApply();
+  CheckValid(env, context.get());
+
+  ASSERT_TRUE(ops[2]->PreconditionHolds());
+  ops[2]->TryToApply();
+  CheckValid(env, context.get());
+
+  std::string expected = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %19 = OpConstant %6 3
+         %29 = OpConstant %6 1
+         %31 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %24 = OpLoad %6 %8
+               OpBranch %28
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  CheckEqual(env, expected, context.get());
+}
+
+TEST(StructuredConstructToBlockReductionPassTest, CannotBeRemovedDueToUses) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpName %100 "temp"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %19 = OpConstant %6 3
+         %29 = OpConstant %6 1
+         %31 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+        %100 = OpCopyObject %10 %11
+               OpBranch %13
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpLoopMerge %16 %17 None
+               OpBranch %15
+         %15 = OpLabel
+         %18 = OpLoad %6 %8
+         %20 = OpSGreaterThan %10 %18 %19
+               OpSelectionMerge %22 None
+               OpBranchConditional %20 %21 %22
+         %21 = OpLabel
+               OpBranch %16
+         %22 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %14
+         %16 = OpLabel
+        %101 = OpCopyObject %6 %18
+         %24 = OpLoad %6 %8
+               OpSelectionMerge %28 None
+               OpSwitch %24 %27 1 %25 2 %26
+         %27 = OpLabel
+               OpStore %8 %19
+        %102 = OpCopyObject %10 %11
+               OpBranch %28
+         %25 = OpLabel
+               OpStore %8 %29
+               OpBranch %28
+         %26 = OpLabel
+               OpStore %8 %31
+               OpBranch %28
+         %28 = OpLabel
+        %103 = OpPhi %10 %102 %27 %11 %25 %11 %26
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredConstructToBlockReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get(), 0);
+  ASSERT_TRUE(ops.empty());
+}
+
+TEST(StructuredConstructToBlockReductionPassTest,
+     CannotBeRemovedDueToOpPhiAtMerge) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+        %101 = OpPhi %10 %11 %5 %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
+  const auto ops = StructuredConstructToBlockReductionOpportunityFinder()
+                       .GetAvailableOpportunities(context.get(), 0);
+  ASSERT_TRUE(ops.empty());
+}
+
+}  // namespace
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/test/test_fixture.h b/test/test_fixture.h
index 436993e..0c5bfc9 100644
--- a/test/test_fixture.h
+++ b/test/test_fixture.h
@@ -46,7 +46,7 @@
     text = {textStr, strlen(textStr)};
   }
 
-  virtual ~TextToBinaryTestBase() {
+  ~TextToBinaryTestBase() override {
     DestroyBinary();
     if (diagnostic) spvDiagnosticDestroy(diagnostic);
   }
diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp
index 023763b..1324206 100644
--- a/test/text_to_binary.extension_test.cpp
+++ b/test/text_to_binary.extension_test.cpp
@@ -905,5 +905,133 @@
              MakeInstruction(SpvOpDecorate, {1, 5300})},
         })));
 
+// SPV_KHR_linkonce_odr
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_KHR_linkonce_odr, ExtensionRoundTripTest,
+    Combine(
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0,
+               SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2),
+        ValuesIn(std::vector<AssemblyCase>{
+            {"OpExtension \"SPV_KHR_linkonce_odr\"\n",
+             MakeInstruction(SpvOpExtension,
+                             MakeVector("SPV_KHR_linkonce_odr"))},
+            {"OpDecorate %1 LinkageAttributes \"foobar\" LinkOnceODR\n",
+             MakeInstruction(SpvOpDecorate,
+                             Concatenate({{1, SpvDecorationLinkageAttributes},
+                                          MakeVector("foobar"),
+                                          {SpvLinkageTypeLinkOnceODR}}))},
+        })));
+
+// SPV_KHR_expect_assume
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_KHR_expect_assume, ExtensionRoundTripTest,
+    Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_3,
+                   SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2),
+            ValuesIn(std::vector<AssemblyCase>{
+                {"OpExtension \"SPV_KHR_expect_assume\"\n",
+                 MakeInstruction(SpvOpExtension,
+                                 MakeVector("SPV_KHR_expect_assume"))},
+                {"OpAssumeTrueKHR %1\n",
+                 MakeInstruction(SpvOpAssumeTrueKHR, {1})}})));
+// SPV_KHR_subgroup_uniform_control_flow
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_KHR_subgroup_uniform_control_flow, ExtensionRoundTripTest,
+    Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_3,
+                   SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2),
+            ValuesIn(std::vector<AssemblyCase>{
+                {"OpExtension \"SPV_KHR_subgroup_uniform_control_flow\"\n",
+                 MakeInstruction(
+                     SpvOpExtension,
+                     MakeVector("SPV_KHR_subgroup_uniform_control_flow"))},
+                {"OpExecutionMode %1 SubgroupUniformControlFlowKHR\n",
+                 MakeInstruction(
+                     SpvOpExecutionMode,
+                     {1, SpvExecutionModeSubgroupUniformControlFlowKHR})},
+            })));
+
+// SPV_KHR_integer_dot_product
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_KHR_integer_dot_product, ExtensionRoundTripTest,
+    Combine(
+        Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_0,
+               SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2),
+        ValuesIn(std::vector<AssemblyCase>{
+            {"OpExtension \"SPV_KHR_integer_dot_product\"\n",
+             MakeInstruction(SpvOpExtension,
+                             MakeVector("SPV_KHR_integer_dot_product"))},
+            {"OpCapability DotProductInputAllKHR\n",
+             MakeInstruction(SpvOpCapability,
+                             {SpvCapabilityDotProductInputAllKHR})},
+            {"OpCapability DotProductInput4x8BitKHR\n",
+             MakeInstruction(SpvOpCapability,
+                             {SpvCapabilityDotProductInput4x8BitKHR})},
+            {"OpCapability DotProductInput4x8BitPackedKHR\n",
+             MakeInstruction(SpvOpCapability,
+                             {SpvCapabilityDotProductInput4x8BitPackedKHR})},
+            {"OpCapability DotProductKHR\n",
+             MakeInstruction(SpvOpCapability, {SpvCapabilityDotProductKHR})},
+            {"%2 = OpSDotKHR %1 %3 %4\n",
+             MakeInstruction(SpvOpSDotKHR, {1, 2, 3, 4})},
+            {"%2 = OpSDotKHR %1 %3 %4 PackedVectorFormat4x8BitKHR\n",
+             MakeInstruction(
+                 SpvOpSDotKHR,
+                 {1, 2, 3, 4,
+                  SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
+            {"%2 = OpUDotKHR %1 %3 %4\n",
+             MakeInstruction(SpvOpUDotKHR, {1, 2, 3, 4})},
+            {"%2 = OpUDotKHR %1 %3 %4 PackedVectorFormat4x8BitKHR\n",
+             MakeInstruction(
+                 SpvOpUDotKHR,
+                 {1, 2, 3, 4,
+                  SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
+            {"%2 = OpSUDotKHR %1 %3 %4\n",
+             MakeInstruction(SpvOpSUDotKHR, {1, 2, 3, 4})},
+            {"%2 = OpSUDotKHR %1 %3 %4 PackedVectorFormat4x8BitKHR\n",
+             MakeInstruction(
+                 SpvOpSUDotKHR,
+                 {1, 2, 3, 4,
+                  SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
+            {"%2 = OpSDotAccSatKHR %1 %3 %4 %5\n",
+             MakeInstruction(SpvOpSDotAccSatKHR, {1, 2, 3, 4, 5})},
+            {"%2 = OpSDotAccSatKHR %1 %3 %4 %5 PackedVectorFormat4x8BitKHR\n",
+             MakeInstruction(
+                 SpvOpSDotAccSatKHR,
+                 {1, 2, 3, 4, 5,
+                  SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
+            {"%2 = OpUDotAccSatKHR %1 %3 %4 %5\n",
+             MakeInstruction(SpvOpUDotAccSatKHR, {1, 2, 3, 4, 5})},
+            {"%2 = OpUDotAccSatKHR %1 %3 %4 %5 PackedVectorFormat4x8BitKHR\n",
+             MakeInstruction(
+                 SpvOpUDotAccSatKHR,
+                 {1, 2, 3, 4, 5,
+                  SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
+            {"%2 = OpSUDotAccSatKHR %1 %3 %4 %5\n",
+             MakeInstruction(SpvOpSUDotAccSatKHR, {1, 2, 3, 4, 5})},
+            {"%2 = OpSUDotAccSatKHR %1 %3 %4 %5 PackedVectorFormat4x8BitKHR\n",
+             MakeInstruction(
+                 SpvOpSUDotAccSatKHR,
+                 {1, 2, 3, 4, 5,
+                  SpvPackedVectorFormatPackedVectorFormat4x8BitKHR})},
+        })));
+
+// SPV_KHR_bit_instructions
+
+INSTANTIATE_TEST_SUITE_P(
+    SPV_KHR_bit_instructions, ExtensionRoundTripTest,
+    Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5,
+                   SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2),
+            ValuesIn(std::vector<AssemblyCase>{
+                {"OpExtension \"SPV_KHR_bit_instructions\"\n",
+                 MakeInstruction(SpvOpExtension,
+                                 MakeVector("SPV_KHR_bit_instructions"))},
+                {"OpCapability BitInstructions\n",
+                 MakeInstruction(SpvOpCapability,
+                                 {SpvCapabilityBitInstructions})},
+            })));
+
 }  // namespace
 }  // namespace spvtools
diff --git a/test/text_to_binary.mode_setting_test.cpp b/test/text_to_binary.mode_setting_test.cpp
index 8ddf421..647bb3d 100644
--- a/test/text_to_binary.mode_setting_test.cpp
+++ b/test/text_to_binary.mode_setting_test.cpp
@@ -189,6 +189,7 @@
                 {CASE(OutputTriangleStrip), {}},
                 {CASE(VecTypeHint), {96}},
                 {CASE(ContractionOff), {}},
+                {CASE(SubgroupUniformControlFlowKHR), {}},
             })));
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/test/text_to_binary_test.cpp b/test/text_to_binary_test.cpp
index 57f0a6c..99d9ed6 100644
--- a/test/text_to_binary_test.cpp
+++ b/test/text_to_binary_test.cpp
@@ -247,12 +247,6 @@
         {"0x1.804p4", 0x00004e01},
     }));
 
-TEST(CreateContext, InvalidEnvironment) {
-  spv_target_env env;
-  std::memset(&env, 99, sizeof(env));
-  EXPECT_THAT(spvContextCreate(env), IsNull());
-}
-
 TEST(CreateContext, UniversalEnvironment) {
   auto c = spvContextCreate(SPV_ENV_UNIVERSAL_1_0);
   EXPECT_THAT(c, NotNull());
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index b17d1cb..64eba44 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -23,6 +23,7 @@
 add_spvtools_unittest(TARGET val_abcde
   SRCS
        val_adjacency_test.cpp
+       val_annotation_test.cpp
        val_arithmetics_test.cpp
        val_atomics_test.cpp
        val_barriers_test.cpp
@@ -38,8 +39,14 @@
        val_entry_point.cpp
        val_explicit_reserved_test.cpp
        val_extensions_test.cpp
+       val_extension_spv_khr_expect_assume.cpp
+       val_extension_spv_khr_linkonce_odr.cpp
+       val_extension_spv_khr_subgroup_uniform_control_flow.cpp
+       val_extension_spv_khr_integer_dot_product.cpp
+       val_extension_spv_khr_bit_instructions.cpp
        val_extension_spv_khr_terminate_invocation.cpp
        val_ext_inst_test.cpp
+       val_ext_inst_debug_test.cpp
        ${VAL_TEST_COMMON_SRCS}
   LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}
   PCH_FILE pch_test_val
diff --git a/test/val/val_annotation_test.cpp b/test/val/val_annotation_test.cpp
new file mode 100644
index 0000000..cd56005
--- /dev/null
+++ b/test/val/val_annotation_test.cpp
@@ -0,0 +1,917 @@
+// Copyright (c) 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Validation tests for decorations
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.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;
+
+using MemberOnlyDecorations = spvtest::ValidateBase<std::string>;
+
+TEST_P(MemberOnlyDecorations, MemberDecoration) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %struct 0 )" +
+                           deco + R"(
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%float2x2 = OpTypeMatrix %float2 2
+%struct = OpTypeStruct %float2x2
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(MemberOnlyDecorations, Decoration) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %struct )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%float2x2 = OpTypeMatrix %float2 2
+%struct = OpTypeStruct %float2x2
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("can only be applied to structure members"));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateMemberOnlyDecorations, MemberOnlyDecorations,
+                         Values("RowMajor", "ColMajor", "MatrixStride 16"
+                                // SPIR-V spec bug?
+                                /*,"Offset 0"*/));
+
+using NonMemberOnlyDecorations = spvtest::ValidateBase<std::string>;
+
+TEST_P(NonMemberOnlyDecorations, MemberDecoration) {
+  const auto deco = GetParam();
+  const auto text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpCapability InputAttachment
+OpCapability Addresses
+OpCapability PhysicalStorageBufferAddresses
+OpCapability ShaderNonUniform
+OpExtension "SPV_KHR_no_integer_wrap_decoration"
+OpExtension "SPV_KHR_physical_storage_buffer"
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_EXT_descriptor_indexing"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %struct 0 )" +
+                    deco + R"(
+%float = OpTypeFloat 32
+%float2 = OpTypeVector %float 2
+%float2x2 = OpTypeMatrix %float2 2
+%struct = OpTypeStruct %float2x2
+)";
+
+  CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("cannot be applied to structure members"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateNonMemberOnlyDecorations, NonMemberOnlyDecorations,
+    Values("SpecId 1", "Block", "BufferBlock", "ArrayStride 4", "GLSLShared",
+           "GLSLPacked", "CPacked",
+           // TODO: https://github.com/KhronosGroup/glslang/issues/703:
+           // glslang applies Restrict to structure members.
+           //"Restrict",
+           "Aliased", "Constant", "Uniform", "SaturatedConversion", "Index 0",
+           "Binding 0", "DescriptorSet 0", "FuncParamAttr Zext",
+           "FPRoundingMode RTE", "FPFastMathMode None",
+           "LinkageAttributes \"ext\" Import", "NoContraction",
+           "InputAttachmentIndex 0", "Alignment 4", "MaxByteOffset 4",
+           "AlignmentId %float", "MaxByteOffsetId %float", "NoSignedWrap",
+           "NoUnsignedWrap", "NonUniform", "RestrictPointer", "AliasedPointer",
+           "CounterBuffer %float"));
+
+using StructDecorations = spvtest::ValidateBase<std::string>;
+
+TEST_P(StructDecorations, Struct) {
+  const std::string deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %struct )" + deco +
+                           R"(
+%struct = OpTypeStruct
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(StructDecorations, OtherType) {
+  const std::string deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %int )" + deco + R"(
+%int = OpTypeInt 32 0
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type"));
+}
+
+TEST_P(StructDecorations, Variable) {
+  const std::string deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var )" + deco + R"(
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer Private %int
+%var = OpVariable %ptr Private
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type"));
+}
+
+TEST_P(StructDecorations, FunctionParameter) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "import" Import
+OpDecorate %param )" + deco +
+                           R"(
+%int = OpTypeInt 32 0
+%void = OpTypeVoid
+%fn = OpTypeFunction %void %int
+%func = OpFunction %void None %fn
+%param = OpFunctionParameter %int
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type"));
+}
+
+TEST_P(StructDecorations, Constant) {
+  const std::string deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %int_0 )" + deco +
+                           R"(
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type"));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateStructDecorations, StructDecorations,
+                         Values("Block", "BufferBlock", "GLSLShared",
+                                "GLSLPacked", "CPacked"));
+
+using ArrayDecorations = spvtest::ValidateBase<std::string>;
+
+TEST_P(ArrayDecorations, Array) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %array )" + deco +
+                           R"(
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array = OpTypeArray %int %int_4
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ArrayDecorations, RuntimeArray) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %array )" + deco +
+                           R"(
+%int = OpTypeInt 32 0
+%array = OpTypeRuntimeArray %int
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ArrayDecorations, Pointer) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %ptr )" + deco + R"(
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer Workgroup %int
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ArrayDecorations, Struct) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %struct )" + deco +
+                           R"(
+%int = OpTypeInt 32 0
+%struct = OpTypeStruct %int
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be an array or pointer type"));
+}
+
+TEST_P(ArrayDecorations, Variable) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var )" + deco + R"(
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer Private %int
+%var = OpVariable %ptr Private
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be an array or pointer type"));
+}
+
+TEST_P(ArrayDecorations, FunctionParameter) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "import" Import
+OpDecorate %param )" + deco +
+                           R"(
+%int = OpTypeInt 32 0
+%void = OpTypeVoid
+%fn = OpTypeFunction %void %int
+%func = OpFunction %void None %fn
+%param = OpFunctionParameter %int
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be an array or pointer type"));
+}
+
+TEST_P(ArrayDecorations, Constant) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %null )" + deco +
+                           R"(
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%array = OpTypeArray %int %int_4
+%null = OpConstantNull %array
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be an array or pointer type"));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateArrayDecorations, ArrayDecorations,
+                         Values("ArrayStride 4"));
+
+using BuiltInDecorations = spvtest::ValidateBase<std::string>;
+
+TEST_P(BuiltInDecorations, Variable) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var BuiltIn )" +
+                           deco + R"(
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer Input %int
+%var = OpVariable %ptr Input
+)";
+
+  CompileSuccessfully(text);
+  if (deco != "WorkgroupSize") {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("must be a constant for WorkgroupSize"));
+  }
+}
+
+TEST_P(BuiltInDecorations, IntegerType) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %int BuiltIn )" +
+                           deco + R"(
+%int = OpTypeInt 32 0
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("BuiltIns can only target variables, structure members "
+                        "or constants"));
+}
+
+TEST_P(BuiltInDecorations, FunctionParameter) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "import" Import
+OpDecorate %param BuiltIn )" +
+                           deco + R"(
+%int = OpTypeInt 32 0
+%void = OpTypeVoid
+%fn = OpTypeFunction %void %int
+%func = OpFunction %void None %fn
+%param = OpFunctionParameter %int
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("BuiltIns can only target variables, structure members "
+                        "or constants"));
+}
+
+TEST_P(BuiltInDecorations, Constant) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %const BuiltIn )" +
+                           deco + R"(
+%int = OpTypeInt 32 0
+%int3 = OpTypeVector %int 3
+%int_1 = OpConstant %int 1
+%const = OpConstantComposite %int3 %int_1 %int_1 %int_1
+)";
+
+  CompileSuccessfully(text);
+  if (deco == "WorkgroupSize") {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable"));
+  }
+}
+
+TEST_P(BuiltInDecorations, SpecConstant) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %const BuiltIn )" +
+                           deco + R"(
+%int = OpTypeInt 32 0
+%int3 = OpTypeVector %int 3
+%int_1 = OpConstant %int 1
+%const = OpSpecConstantComposite %int3 %int_1 %int_1 %int_1
+)";
+
+  CompileSuccessfully(text);
+  if (deco == "WorkgroupSize") {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  } else {
+    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable"));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateBuiltInDecorations, BuiltInDecorations,
+                         Values("Position", "PointSize", "VertexId",
+                                "InstanceId", "FragCoord", "FrontFacing",
+                                "NumWorkgroups", "WorkgroupSize",
+                                "LocalInvocationId", "GlobalInvocationId"));
+
+using MemoryObjectDecorations = spvtest::ValidateBase<std::string>;
+
+TEST_P(MemoryObjectDecorations, Variable) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability SampleRateShading
+OpCapability TransformFeedback
+OpCapability GeometryStreams
+OpCapability Tessellation
+OpCapability PhysicalStorageBufferAddresses
+OpExtension "SPV_KHR_physical_storage_buffer"
+OpMemoryModel Logical GLSL450
+OpDecorate %var )" + deco + R"(
+%float = OpTypeFloat 32
+%ptr = OpTypePointer Input %float
+%var = OpVariable %ptr Input
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(MemoryObjectDecorations, FunctionParameterGood) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability SampleRateShading
+OpCapability TransformFeedback
+OpCapability GeometryStreams
+OpCapability Tessellation
+OpCapability PhysicalStorageBufferAddresses
+OpExtension "SPV_KHR_physical_storage_buffer"
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "import" Import
+OpDecorate %param )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+%ptr = OpTypePointer Input %float
+%void = OpTypeVoid
+%fn = OpTypeFunction %void %ptr
+%func = OpFunction %void None %fn
+%param = OpFunctionParameter %ptr
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(MemoryObjectDecorations, FunctionParameterNotAPointer) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability SampleRateShading
+OpCapability TransformFeedback
+OpCapability GeometryStreams
+OpCapability Tessellation
+OpCapability PhysicalStorageBufferAddresses
+OpExtension "SPV_KHR_physical_storage_buffer"
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "import" Import
+OpDecorate %param )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+%void = OpTypeVoid
+%fn = OpTypeFunction %void %float
+%func = OpFunction %void None %fn
+%param = OpFunctionParameter %float
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a pointer type"));
+}
+
+TEST_P(MemoryObjectDecorations, FloatType) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability SampleRateShading
+OpCapability TransformFeedback
+OpCapability GeometryStreams
+OpCapability Tessellation
+OpCapability PhysicalStorageBufferAddresses
+OpExtension "SPV_KHR_physical_storage_buffer"
+OpMemoryModel Logical GLSL450
+OpDecorate %float )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be a memory object declaration"));
+}
+
+TEST_P(MemoryObjectDecorations, Constant) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability SampleRateShading
+OpCapability TransformFeedback
+OpCapability GeometryStreams
+OpCapability Tessellation
+OpCapability PhysicalStorageBufferAddresses
+OpExtension "SPV_KHR_physical_storage_buffer"
+OpMemoryModel Logical GLSL450
+OpDecorate %const )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+%const = OpConstant %float 0
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be a memory object declaration"));
+}
+
+// NonWritable and NonReadable are covered by other tests.
+INSTANTIATE_TEST_SUITE_P(
+    ValidateMemoryObjectDecorations, MemoryObjectDecorations,
+    Values("NoPerspective", "Flat", "Patch", "Centroid", "Component 0",
+           "Sample", "Restrict", "Aliased", "Volatile", "Coherent", "Stream 0",
+           "XfbBuffer 1", "XfbStride 1", "AliasedPointer", "RestrictPointer"));
+
+using VariableDecorations = spvtest::ValidateBase<std::string>;
+
+TEST_P(VariableDecorations, Variable) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpCapability InputAttachment
+OpMemoryModel Logical GLSL450
+OpDecorate %var )" + deco + R"(
+%float = OpTypeFloat 32
+%ptr = OpTypePointer Input %float
+%var = OpVariable %ptr Input
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(VariableDecorations, FunctionParameter) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpCapability InputAttachment
+OpMemoryModel Logical GLSL450
+OpDecorate %func LinkageAttributes "import" Import
+OpDecorate %param )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+%void = OpTypeVoid
+%fn = OpTypeFunction %void %float
+%func = OpFunction %void None %fn
+%param = OpFunctionParameter %float
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable"));
+}
+
+TEST_P(VariableDecorations, FloatType) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpCapability InputAttachment
+OpMemoryModel Logical GLSL450
+OpDecorate %float )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable"));
+}
+
+TEST_P(VariableDecorations, Constant) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpCapability InputAttachment
+OpMemoryModel Logical GLSL450
+OpDecorate %const )" + deco +
+                           R"(
+%float = OpTypeFloat 32
+%const = OpConstant %float 0
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable"));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateVariableDecorations, VariableDecorations,
+                         Values("Invariant", "Constant", "Location 0",
+                                "Index 0", "Binding 0", "DescriptorSet 0"));
+
+using VulkanIOStorageClass =
+    spvtest::ValidateBase<std::tuple<std::string, std::string>>;
+
+TEST_P(VulkanIOStorageClass, Invalid) {
+  const auto deco = std::get<0>(GetParam());
+  const auto sc = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var )" + deco + R"( 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%ptr = OpTypePointer )" +
+                           sc +
+                           R"( %float
+%var = OpVariable %ptr )" + sc +
+                           R"(
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("decoration must not be applied to this storage class"));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateVulkanIOStorageClass, VulkanIOStorageClass,
+                         Combine(Values("Location", "Component"),
+                                 Values("StorageBuffer", "Uniform",
+                                        "UniformConstant", "Workgroup",
+                                        "Private")));
+
+using VulkanResourceStorageClass =
+    spvtest::ValidateBase<std::tuple<std::string, std::string>>;
+
+TEST_P(VulkanResourceStorageClass, Invalid) {
+  const auto deco = std::get<0>(GetParam());
+  const auto sc = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var )" + deco + R"( 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%ptr = OpTypePointer )" +
+                           sc +
+                           R"( %float
+%var = OpVariable %ptr )" + sc +
+                           R"(
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be in the StorageBuffer, Uniform, or "
+                        "UniformConstant storage class"));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateVulkanResourceStorageClass,
+                         VulkanResourceStorageClass,
+                         Combine(Values("DescriptorSet", "Binding"),
+                                 Values("Private", "Input", "Output",
+                                        "Workgroup")));
+
+using VulkanInterpolationStorageClass = spvtest::ValidateBase<std::string>;
+
+TEST_P(VulkanInterpolationStorageClass, Input) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability SampleRateShading
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var )" + deco + R"(
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%void_fn = OpTypeFunction %void
+%ptr = OpTypePointer Input %float
+%var = OpVariable %ptr Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_P(VulkanInterpolationStorageClass, Output) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability SampleRateShading
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpDecorate %var )" + deco + R"(
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%void_fn = OpTypeFunction %void
+%ptr = OpTypePointer Output %float
+%var = OpVariable %ptr Output
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_P(VulkanInterpolationStorageClass, Private) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability SampleRateShading
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var )" + deco + R"(
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%void_fn = OpTypeFunction %void
+%ptr = OpTypePointer Private %float
+%var = OpVariable %ptr Private
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("storage class must be Input or Output"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("[VUID-StandaloneSpirv-Flat-04670"));
+}
+
+TEST_P(VulkanInterpolationStorageClass, Uniform) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability SampleRateShading
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var )" + deco + R"(
+OpDecorate %var Binding 0
+OpDecorate %var DescriptorSet 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%void_fn = OpTypeFunction %void
+%ptr = OpTypePointer Uniform %float
+%var = OpVariable %ptr Uniform
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("storage class must be Input or Output"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("[VUID-StandaloneSpirv-Flat-04670"));
+}
+
+TEST_P(VulkanInterpolationStorageClass, StorageBuffer) {
+  const auto deco = GetParam();
+  const std::string text = R"(
+OpCapability Shader
+OpCapability SampleRateShading
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var )" + deco + R"(
+OpDecorate %var Binding 0
+OpDecorate %var DescriptorSet 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%void_fn = OpTypeFunction %void
+%ptr = OpTypePointer StorageBuffer %float
+%var = OpVariable %ptr StorageBuffer
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("storage class must be Input or Output"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("[VUID-StandaloneSpirv-Flat-04670"));
+}
+
+INSTANTIATE_TEST_SUITE_P(ValidateVulkanInterpolationStorageClass,
+                         VulkanInterpolationStorageClass,
+                         Values("Flat", "NoPerspective", "Centroid", "Sample"));
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp
index b82fc97..856ad02 100644
--- a/test/val/val_arithmetics_test.cpp
+++ b/test/val/val_arithmetics_test.cpp
@@ -1309,6 +1309,57 @@
       HasSubstr("Cooperative matrix 'M' mismatch: CooperativeMatrixMulAddNV"));
 }
 
+TEST_F(ValidateArithmetics, CoopMatComponentTypeNotScalarNumeric) {
+  const std::string types = R"(
+%bad = OpTypeCooperativeMatrixNV %bool %subgroup %u32_8 %u32_8
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode(types, "").c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpTypeCooperativeMatrixNV Component Type <id> "
+                        "'4[%bool]' is not a scalar numerical type."));
+}
+
+TEST_F(ValidateArithmetics, CoopMatScopeNotConstantInt) {
+  const std::string types = R"(
+%bad = OpTypeCooperativeMatrixNV %f16 %f32_1 %u32_8 %u32_8
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode(types, "").c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTypeCooperativeMatrixNV Scope <id> '17[%float_1]' is not a "
+                "constant instruction with scalar integer type."));
+}
+
+TEST_F(ValidateArithmetics, CoopMatRowsNotConstantInt) {
+  const std::string types = R"(
+%bad = OpTypeCooperativeMatrixNV %f16 %subgroup %f32_1 %u32_8
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode(types, "").c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTypeCooperativeMatrixNV Rows <id> '17[%float_1]' is not a "
+                "constant instruction with scalar integer type."));
+}
+
+TEST_F(ValidateArithmetics, CoopMatColumnsNotConstantInt) {
+  const std::string types = R"(
+%bad = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %f32_1
+)";
+
+  CompileSuccessfully(GenerateCoopMatCode(types, "").c_str());
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("OpTypeCooperativeMatrixNV Cols <id> '17[%float_1]' is not a "
+                "constant instruction with scalar integer type."));
+}
+
 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 c7e36f5..d1a030a 100644
--- a/test/val/val_atomics_test.cpp
+++ b/test/val/val_atomics_test.cpp
@@ -95,12 +95,13 @@
 std::string GenerateShaderCode(
     const std::string& body,
     const std::string& capabilities_and_extensions = "",
+    const std::string& extra_defs = "",
     const std::string& memory_model = "GLSL450") {
   const std::string execution = R"(
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 )";
-  const std::string defintions = R"(
+  const std::string definitions = R"(
 %u64 = OpTypeInt 64 0
 %s64 = OpTypeInt 64 1
 
@@ -113,19 +114,20 @@
 %s64_var = OpVariable %s64_ptr Workgroup
 )";
   return GenerateShaderCodeImpl(
-      body, "OpCapability Int64\n" + capabilities_and_extensions, defintions,
-      memory_model, execution);
+      body, "OpCapability Int64\n" + capabilities_and_extensions,
+      definitions + extra_defs, memory_model, execution);
 }
 
 std::string GenerateShaderComputeCode(
     const std::string& body,
     const std::string& capabilities_and_extensions = "",
+    const std::string& extra_defs = "",
     const std::string& memory_model = "GLSL450") {
   const std::string execution = R"(
 OpEntryPoint GLCompute %main "main"
 OpExecutionMode %main LocalSize 32 1 1
 )";
-  const std::string defintions = R"(
+  const std::string definitions = R"(
 %u64 = OpTypeInt 64 0
 %s64 = OpTypeInt 64 1
 
@@ -138,8 +140,8 @@
 %s64_var = OpVariable %s64_ptr Workgroup
 )";
   return GenerateShaderCodeImpl(
-      body, "OpCapability Int64\n" + capabilities_and_extensions, defintions,
-      memory_model, execution);
+      body, "OpCapability Int64\n" + capabilities_and_extensions,
+      definitions + extra_defs, memory_model, execution);
 }
 
 std::string GenerateKernelCode(
@@ -222,7 +224,6 @@
   const std::string body = R"(
 %val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
 %val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
-%val3 = OpAtomicLoad %u64 %u64_var %subgroup %sequentially_consistent
 )";
 
   CompileSuccessfully(GenerateShaderCode(body));
@@ -233,23 +234,56 @@
   const std::string body = R"(
 %val1 = OpAtomicLoad %f32 %f32_var %device %relaxed
 %val2 = OpAtomicLoad %u32 %u32_var %workgroup %sequentially_consistent
-%val3 = OpAtomicLoad %u64 %u64_var %subgroup %acquire
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateAtomics, AtomicLoadInt64ShaderSuccess) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u64 %u64_var %subgroup %sequentially_consistent
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, "OpCapability Int64Atomics\n"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateAtomics, AtomicLoadInt64KernelSuccess) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u64 %u64_var %subgroup %acquire
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body, "OpCapability Int64Atomics\n"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 TEST_F(ValidateAtomics, AtomicLoadInt32VulkanSuccess) {
   const std::string body = R"(
 %val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
 %val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire
+%val3 = OpAtomicLoad %u32 %u32_var %invocation %relaxed
 )";
 
   CompileSuccessfully(GenerateShaderComputeCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateAtomics, AtomicLoadVulkanWrongStorageClass) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04645"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("in Vulkan evironment, Workgroup Storage Class is limited to "
+                "MeshNV, TaskNV, and GLCompute execution model"));
+}
+
 TEST_F(ValidateAtomics, AtomicAddIntVulkanWrongType1) {
   const std::string body = R"(
 %val1 = OpAtomicIAdd %f32 %f32_var %device %relaxed %f32_1
@@ -259,7 +293,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicIAdd: "
-                        "expected Result Type to be int scalar type"));
+                        "expected Result Type to be integer scalar type"));
 }
 
 TEST_F(ValidateAtomics, AtomicAddIntVulkanWrongType2) {
@@ -284,7 +318,33 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Opcode AtomicFAddEXT requires one of these capabilities: "
-                "AtomicFloat32AddEXT AtomicFloat64AddEXT"));
+                "AtomicFloat32AddEXT AtomicFloat64AddEXT AtomicFloat16AddEXT"));
+}
+
+TEST_F(ValidateAtomics, AtomicMinFloatVulkan) {
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %f32 %f32_var %device %relaxed %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Opcode AtomicFMinEXT requires one of these capabilities: "
+                "AtomicFloat32MinMaxEXT AtomicFloat64MinMaxEXT AtomicFloat16MinMaxEXT"));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloatVulkan) {
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %f32 %f32_var %device %relaxed %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Opcode AtomicFMaxEXT requires one of these capabilities: "
+                "AtomicFloat32MinMaxEXT AtomicFloat64MinMaxEXT AtomicFloat16MinMaxEXT"));
 }
 
 TEST_F(ValidateAtomics, AtomicAddFloatVulkanWrongType1) {
@@ -303,6 +363,38 @@
                         "expected Result Type to be float scalar type"));
 }
 
+TEST_F(ValidateAtomics, AtomicMinFloatVulkanWrongType1) {
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %f32vec4 %f32vec4_var %device %relaxed %f32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMinEXT: "
+                        "expected Result Type to be float scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloatVulkanWrongType1) {
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %f32vec4 %f32vec4_var %device %relaxed %f32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMaxEXT: "
+                        "expected Result Type to be float scalar type"));
+}
+
 TEST_F(ValidateAtomics, AtomicAddFloatVulkanWrongType2) {
   const std::string body = R"(
 %val1 = OpAtomicFAddEXT %u32 %u32_var %device %relaxed %u32_1
@@ -319,6 +411,38 @@
                         "expected Result Type to be float scalar type"));
 }
 
+TEST_F(ValidateAtomics, AtomicMinFloatVulkanWrongType2) {
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %u32 %u32_var %device %relaxed %u32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMinEXT: "
+                        "expected Result Type to be float scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloatVulkanWrongType2) {
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %u32 %u32_var %device %relaxed %u32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMaxEXT: "
+                        "expected Result Type to be float scalar type"));
+}
+
 TEST_F(ValidateAtomics, AtomicAddFloatVulkanWrongType3) {
   const std::string body = R"(
 %val1 = OpAtomicFAddEXT %u64 %u64_var %device %relaxed %u64_1
@@ -335,6 +459,38 @@
                         "expected Result Type to be float scalar type"));
 }
 
+TEST_F(ValidateAtomics, AtomicMinFloatVulkanWrongType3) {
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %u64 %u64_var %device %relaxed %u64_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMinEXT: "
+                        "expected Result Type to be float scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloatVulkanWrongType3) {
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %u64 %u64_var %device %relaxed %u64_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMaxEXT: "
+                        "expected Result Type to be float scalar type"));
+}
+
 TEST_F(ValidateAtomics, AtomicAddFloatVulkanWrongCapability) {
   const std::string body = R"(
 %val1 = OpAtomicFAddEXT %f32 %f32_var %device %relaxed %f32_1
@@ -351,16 +507,183 @@
                         "require the AtomicFloat32AddEXT capability"));
 }
 
+TEST_F(ValidateAtomics, AtomicMinFloatVulkanWrongCapability) {
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %f32 %f32_var %device %relaxed %f32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat64MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMinEXT: float min/max atomics "
+                        "require the AtomicFloat32MinMaxEXT capability"));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloatVulkanWrongCapability) {
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %f32 %f32_var %device %relaxed %f32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat64MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicFMaxEXT: float min/max atomics "
+                        "require the AtomicFloat32MinMaxEXT capability"));
+}
+
+TEST_F(ValidateAtomics, AtomicAddFloat16VulkanSuccess) {
+  const std::string defs = R"(
+%f16 = OpTypeFloat 16
+%f16_1 = OpConstant %f16 1
+%f16_ptr = OpTypePointer Workgroup %f16
+%f16_var = OpVariable %f16_ptr Workgroup
+)";
+  const std::string body = R"(
+%val1 = OpAtomicFAddEXT %f16 %f16_var %device %relaxed %f16_1
+)";
+  const std::string extra = R"(
+OpCapability Float16
+OpCapability AtomicFloat16AddEXT
+OpExtension "SPV_EXT_shader_atomic_float16_add"
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra, defs),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 TEST_F(ValidateAtomics, AtomicAddFloatVulkanSuccess) {
   const std::string body = R"(
 %val1 = OpAtomicFAddEXT %f32 %f32_var %device %relaxed %f32_1
+%val2 = OpAtomicFAddEXT %f32 %f32_var %invocation %relaxed %f32_1
 )";
   const std::string extra = R"(
 OpCapability AtomicFloat32AddEXT
 OpExtension "SPV_EXT_shader_atomic_float_add"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra), SPV_ENV_VULKAN_1_0);
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicMinFloat16VulkanSuccess) {
+  const std::string defs = R"(
+%f16 = OpTypeFloat 16
+%f16_1 = OpConstant %f16 1
+%f16_ptr = OpTypePointer Workgroup %f16
+%f16_var = OpVariable %f16_ptr Workgroup
+)";
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %f16 %f16_var %device %relaxed %f16_1
+)";
+  const std::string extra = R"(
+OpCapability Float16
+OpCapability AtomicFloat16MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra, defs),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloat16VulkanSuccess) {
+  const std::string defs = R"(
+%f16 = OpTypeFloat 16
+%f16_1 = OpConstant %f16 1
+%f16_ptr = OpTypePointer Workgroup %f16
+%f16_var = OpVariable %f16_ptr Workgroup
+)";
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %f16 %f16_var %device %relaxed %f16_1
+)";
+  const std::string extra = R"(
+OpCapability Float16
+OpCapability AtomicFloat16MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra, defs),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicMinFloat32VulkanSuccess) {
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %f32 %f32_var %device %relaxed %f32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloat32VulkanSuccess) {
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %f32 %f32_var %device %relaxed %f32_1
+)";
+  const std::string extra = R"(
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicMinFloat64VulkanSuccess) {
+  const std::string defs = R"(
+%f64 = OpTypeFloat 64
+%f64_1 = OpConstant %f64 1
+%f64_ptr = OpTypePointer Workgroup %f64
+%f64_var = OpVariable %f64_ptr Workgroup
+)";
+  const std::string body = R"(
+%val1 = OpAtomicFMinEXT %f64 %f64_var %device %relaxed %f64_1
+)";
+  const std::string extra = R"(
+OpCapability Float64
+OpCapability AtomicFloat64MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra, defs),
+                      SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateAtomics, AtomicMaxFloat64VulkanSuccess) {
+  const std::string defs = R"(
+%f64 = OpTypeFloat 64
+%f64_1 = OpConstant %f64 1
+%f64_ptr = OpTypePointer Workgroup %f64
+%f64_var = OpVariable %f64_ptr Workgroup
+)";
+  const std::string body = R"(
+%val1 = OpAtomicFMaxEXT %f64 %f64_var %device %relaxed %f64_1
+)";
+  const std::string extra = R"(
+OpCapability Float64
+OpCapability AtomicFloat64MinMaxEXT
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra, defs),
+                      SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
@@ -374,12 +697,27 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
-TEST_F(ValidateAtomics, AtomicStoreFloatVulkan) {
+TEST_F(ValidateAtomics, AtomicStoreVulkanWrongStorageClass) {
   const std::string body = R"(
 OpAtomicStore %f32_var %device %relaxed %f32_1
 )";
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04645"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("in Vulkan evironment, Workgroup Storage Class is limited to "
+                "MeshNV, TaskNV, and GLCompute execution model"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreFloatVulkan) {
+  const std::string body = R"(
+OpAtomicStore %f32_var %device %relaxed %f32_1
+)";
+
+  CompileSuccessfully(GenerateShaderComputeCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
@@ -388,7 +726,7 @@
 %val2 = OpAtomicExchange %f32 %f32_var %device %relaxed %f32_0
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  CompileSuccessfully(GenerateShaderComputeCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
@@ -396,6 +734,7 @@
   const std::string body = R"(
   %val1 = OpAtomicLoad %u64 %u64_var %device %relaxed
   %val2 = OpAtomicLoad %u64 %u64_var %workgroup %acquire
+  %val3 = OpAtomicLoad %u64 %u64_var %invocation %relaxed
   )";
 
   CompileSuccessfully(
@@ -477,6 +816,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpAtomicLoad-04731"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Vulkan spec disallows OpAtomicLoad with Memory Semantics "
@@ -490,6 +831,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpAtomicLoad-04731"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Vulkan spec disallows OpAtomicLoad with Memory Semantics "
@@ -503,12 +846,29 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpAtomicLoad-04731"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Vulkan spec disallows OpAtomicLoad with Memory Semantics "
                 "Release, AcquireRelease and SequentiallyConsistent"));
 }
 
+TEST_F(ValidateAtomics, AtomicLoadVulkanInvocationSemantics) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u32 %u32_var %invocation %acquire
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04641"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: Vulkan specification requires Memory Semantics to "
+                "be None if used with Invocation Memory Scope"));
+}
+
 TEST_F(ValidateAtomics, AtomicLoadShaderFloat) {
   const std::string body = R"(
 %val1 = OpAtomicLoad %f32 %f32_var %device %relaxed
@@ -531,6 +891,45 @@
           "AtomicLoad: 64-bit atomics require the Int64Atomics capability"));
 }
 
+TEST_F(ValidateAtomics, AtomicLoadKernelInt64) {
+  const std::string body = R"(
+%val1 = OpAtomicLoad %u64 %u64_var %device %relaxed
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "AtomicLoad: 64-bit atomics require the Int64Atomics capability"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreVulkanInt64) {
+  const std::string body = R"(
+OpAtomicStore %u64_var %device %relaxed %u64_1
+)";
+
+  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: 64-bit atomics require the Int64Atomics capability"));
+}
+
+TEST_F(ValidateAtomics, AtomicStoreKernelInt64) {
+  const std::string body = R"(
+OpAtomicStore %u64_var %device %relaxed %u64_1
+)";
+
+  CompileSuccessfully(GenerateKernelCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "AtomicStore: 64-bit atomics require the Int64Atomics capability"));
+}
+
 TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) {
   const std::string body = R"(
 %val1 = OpAtomicUMin %u64 %u64_var %device %relaxed %u64_1
@@ -562,8 +961,9 @@
 OpAtomicStore %s64_var %device %relaxed %s64_1
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, "OpCapability Int64Atomics\n"),
-                      SPV_ENV_VULKAN_1_0);
+  CompileSuccessfully(
+      GenerateShaderComputeCode(body, "OpCapability Int64Atomics\n"),
+      SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
@@ -587,9 +987,10 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("AtomicLoad: "
-                        "expected Result Type to be int or float scalar type"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicLoad: "
+                "expected Result Type to be integer or float scalar type"));
 }
 
 TEST_F(ValidateAtomics, AtomicLoadWrongPointerType) {
@@ -662,9 +1063,10 @@
 TEST_F(ValidateAtomics, AtomicStoreVulkanSuccess) {
   const std::string body = R"(
 OpAtomicStore %u32_var %device %release %u32_1
+OpAtomicStore %u32_var %invocation %relaxed %u32_1
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  CompileSuccessfully(GenerateShaderComputeCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
@@ -675,6 +1077,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpAtomicStore-04730"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Vulkan spec disallows OpAtomicStore with Memory Semantics "
@@ -688,6 +1092,8 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpAtomicStore-04730"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Vulkan spec disallows OpAtomicStore with Memory Semantics "
@@ -701,12 +1107,29 @@
 
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpAtomicStore-04730"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Vulkan spec disallows OpAtomicStore with Memory Semantics "
                 "Acquire, AcquireRelease and SequentiallyConsistent"));
 }
 
+TEST_F(ValidateAtomics, AtomicStoreVulkanInvocationSemantics) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %invocation %acquire %u32_1
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04641"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicStore: Vulkan specification requires Memory Semantics "
+                "to be None if used with Invocation Memory Scope"));
+}
+
 TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) {
   const std::string body = R"(
 OpAtomicStore %f32_1 %device %relaxed %f32_1
@@ -728,9 +1151,9 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("AtomicStore: "
-                "expected Pointer to be a pointer to int or float scalar "
-                "type"));
+      HasSubstr(
+          "AtomicStore: "
+          "expected Pointer to be a pointer to integer or float scalar type"));
 }
 
 TEST_F(ValidateAtomics, AtomicStoreWrongPointerStorageTypeForOpenCL) {
@@ -836,9 +1259,10 @@
 
   CompileSuccessfully(GenerateKernelCode(body));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("AtomicExchange: "
-                        "expected Result Type to be int or float scalar type"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicExchange: "
+                "expected Result Type to be integer or float scalar type"));
 }
 
 TEST_F(ValidateAtomics, AtomicExchangeWrongPointerType) {
@@ -906,6 +1330,22 @@
                         "expected Value to be of type Result Type"));
 }
 
+TEST_F(ValidateAtomics, AtomicExchangeVulkanInvocationSemantics) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %invocation %relaxed %u32_1
+%val2 = OpAtomicExchange %u32 %u32_var %invocation %acquire %u32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04641"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicExchange: Vulkan specification requires Memory "
+                "Semantics to be None if used with Invocation Memory Scope"));
+}
+
 TEST_F(ValidateAtomics, AtomicCompareExchangeShaderSuccess) {
   const std::string body = R"(
 OpAtomicStore %u32_var %device %relaxed %u32_1
@@ -918,10 +1358,8 @@
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeKernelSuccess) {
   const std::string body = R"(
-OpAtomicStore %f32_var %device %relaxed %f32_1
-%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %relaxed %f32_0 %f32_1
 OpAtomicStore %u32_var %device %relaxed %u32_1
-%val4 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0
+%val2 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
@@ -938,7 +1376,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicCompareExchange: "
-                        "expected Result Type to be int scalar type"));
+                        "expected Result Type to be integer scalar type"));
 }
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongResultType) {
@@ -951,7 +1389,7 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicCompareExchange: "
-                        "expected Result Type to be int or float scalar type"));
+                        "expected Result Type to be integer scalar type"));
 }
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerType) {
@@ -969,7 +1407,7 @@
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerDataType) {
   const std::string body = R"(
 OpStore %f32vec4_var %f32vec4_0000
-%val2 = OpAtomicCompareExchange %f32 %f32vec4_var %device %relaxed %relaxed %f32_0 %f32_1
+%val2 = OpAtomicCompareExchange %u32 %f32vec4_var %device %relaxed %relaxed %u32_0 %u32_0
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
@@ -982,11 +1420,11 @@
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongScopeType) {
   const std::string body = R"(
-OpAtomicStore %f32_var %device %relaxed %f32_1
-%val2 = OpAtomicCompareExchange %f32 %f32_var %f32_1 %relaxed %relaxed %f32_0 %f32_0
+OpAtomicStore %u64_var %device %relaxed %u64_1
+%val2 = OpAtomicCompareExchange %u64 %u64_var %u64_1 %relaxed %relaxed %u32_0 %u32_0
 )";
 
-  CompileSuccessfully(GenerateKernelCode(body));
+  CompileSuccessfully(GenerateKernelCode(body, "OpCapability Int64Atomics\n"));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicCompareExchange: expected scope to be a 32-bit "
@@ -995,8 +1433,8 @@
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType1) {
   const std::string body = R"(
-OpAtomicStore %f32_var %device %relaxed %f32_1
-%val2 = OpAtomicCompareExchange %f32 %f32_var %device %f32_1 %relaxed %f32_0 %f32_0
+OpAtomicStore %u32_var %device %relaxed %u32_1
+%val2 = OpAtomicCompareExchange %u32 %u32_var %device %f32_1 %relaxed %u32_0 %u32_0
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
@@ -1008,8 +1446,8 @@
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType2) {
   const std::string body = R"(
-OpAtomicStore %f32_var %device %relaxed %f32_1
-%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %f32_1 %f32_0 %f32_0
+OpAtomicStore %u32_var %device %relaxed %u32_1
+%val2 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %f32_1 %u32_0 %u32_0
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
@@ -1021,8 +1459,8 @@
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeUnequalRelease) {
   const std::string body = R"(
-OpAtomicStore %f32_var %device %relaxed %f32_1
-%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %release %f32_0 %f32_0
+OpAtomicStore %u32_var %device %relaxed %u32_1
+%val2 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %release %u32_0 %u32_0
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
@@ -1034,8 +1472,8 @@
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongValueType) {
   const std::string body = R"(
-OpAtomicStore %f32_var %device %relaxed %f32_1
-%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %relaxed %u32_0 %f32_1
+OpAtomicStore %u32_var %device %relaxed %u32_1
+%val2 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %relaxed %f32_1 %u32_0
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
@@ -1047,8 +1485,8 @@
 
 TEST_F(ValidateAtomics, AtomicCompareExchangeWrongComparatorType) {
   const std::string body = R"(
-OpAtomicStore %f32_var %device %relaxed %f32_1
-%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %relaxed %f32_0 %u32_1
+OpAtomicStore %u32_var %device %relaxed %u32_1
+%val2 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %relaxed %u32_0 %f32_0
 )";
 
   CompileSuccessfully(GenerateKernelCode(body));
@@ -1078,7 +1516,39 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("AtomicCompareExchangeWeak: "
-                        "expected Result Type to be int scalar type"));
+                        "expected Result Type to be integer scalar type"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeVulkanInvocationSemanticsEqual) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %device %relaxed %u32_1
+%val2 = OpAtomicCompareExchange %u32 %u32_var %invocation %release %relaxed %u32_0 %u32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04641"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicCompareExchange: Vulkan specification requires Memory "
+                "Semantics to be None if used with Invocation Memory Scope"));
+}
+
+TEST_F(ValidateAtomics, AtomicCompareExchangeVulkanInvocationSemanticsUnequal) {
+  const std::string body = R"(
+OpAtomicStore %u32_var %device %relaxed %u32_1
+%val2 = OpAtomicCompareExchange %u32 %u32_var %invocation %relaxed %acquire %u32_0 %u32_0
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04641"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("AtomicCompareExchange: Vulkan specification requires Memory "
+                "Semantics to be None if used with Invocation Memory Scope"));
 }
 
 TEST_F(ValidateAtomics, AtomicArithmeticsSuccess) {
@@ -1145,7 +1615,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicFlagTestAndSet: "
-                "expected Pointer to point to a value of 32-bit int type"));
+                "expected Pointer to point to a value of 32-bit integer type"));
 }
 
 TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotInt32Pointer) {
@@ -1153,12 +1623,12 @@
 %val1 = OpAtomicFlagTestAndSet %bool %u64_var %device %relaxed
 )";
 
-  CompileSuccessfully(GenerateKernelCode(body));
+  CompileSuccessfully(GenerateKernelCode(body, "OpCapability Int64Atomics\n"));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicFlagTestAndSet: "
-                "expected Pointer to point to a value of 32-bit int type"));
+                "expected Pointer to point to a value of 32-bit integer type"));
 }
 
 TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongScopeType) {
@@ -1219,7 +1689,7 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicFlagClear: "
-                "expected Pointer to point to a value of 32-bit int type"));
+                "expected Pointer to point to a value of 32-bit integer type"));
 }
 
 TEST_F(ValidateAtomics, AtomicFlagClearNotInt32Pointer) {
@@ -1227,12 +1697,12 @@
 OpAtomicFlagClear %u64_var %device %relaxed
 )";
 
-  CompileSuccessfully(GenerateKernelCode(body));
+  CompileSuccessfully(GenerateKernelCode(body, "OpCapability Int64Atomics\n"));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("AtomicFlagClear: "
-                "expected Pointer to point to a value of 32-bit int type"));
+                "expected Pointer to point to a value of 32-bit integer type"));
 }
 
 TEST_F(ValidateAtomics, AtomicFlagClearWrongScopeType) {
@@ -1330,7 +1800,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1349,7 +1819,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1369,7 +1839,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1389,7 +1859,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1409,7 +1879,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1429,7 +1899,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1449,7 +1919,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1468,7 +1938,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1487,7 +1957,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1506,7 +1976,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1525,7 +1995,28 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
+                      SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("SequentiallyConsistent memory semantics cannot be "
+                        "used with the VulkanKHR memory model."));
+}
+
+TEST_F(ValidateAtomics, VulkanMemoryModelBanSequentiallyConsistentAtomicFMinEXT) {
+  const std::string body = R"(
+%max = OpAtomicFMinEXT %f32 %f32_var %workgroup %sequentially_consistent %f32_0
+)";
+
+  const std::string extra = R"(
+OpCapability VulkanMemoryModelKHR
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1544,7 +2035,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1563,7 +2054,28 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
+                      SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("SequentiallyConsistent memory semantics cannot be "
+                        "used with the VulkanKHR memory model."));
+}
+
+TEST_F(ValidateAtomics, VulkanMemoryModelBanSequentiallyConsistentAtomicFMaxEXT) {
+  const std::string body = R"(
+%max = OpAtomicFMaxEXT %f32 %f32_var %workgroup %sequentially_consistent %f32_0
+)";
+
+  const std::string extra = R"(
+OpCapability VulkanMemoryModelKHR
+OpCapability AtomicFloat32MinMaxEXT
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpExtension "SPV_EXT_shader_atomic_float_min_max"
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1582,7 +2094,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1601,7 +2113,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1620,7 +2132,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1844,7 +2356,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderComputeCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
 }
@@ -1972,7 +2484,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
@@ -1992,7 +2504,7 @@
 OpExtension "SPV_KHR_vulkan_memory_model"
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body, extra, "VulkanKHR"),
+  CompileSuccessfully(GenerateShaderCode(body, extra, "", "VulkanKHR"),
                       SPV_ENV_UNIVERSAL_1_3);
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
 }
@@ -2167,6 +2679,39 @@
                         "CooperativeMatrixNV capability is present"));
 }
 
+TEST_F(ValidateAtomics, IIncrementBadPointerDataType) {
+  const std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+     %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+       %void = OpTypeVoid
+         %16 = OpTypeFunction %void
+%uint_538976288 = OpConstant %uint 538976288
+        %int = OpTypeInt 32 1
+%_runtimearr_int = OpTypeRuntimeArray %int
+  %_struct_5 = OpTypeStruct %_runtimearr_int
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+          %3 = OpVariable %_ptr_Input_v3uint Input
+          %7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+       %8224 = OpFunction %void None %16
+      %65312 = OpLabel
+         %25 = OpAccessChain %_ptr_Input_uint %3 %uint_538976288
+         %26 = OpLoad %uint %25
+    %2097184 = OpAtomicIIncrement %int %7 %uint_538976288 %26
+               OpUnreachable
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("AtomicIIncrement: expected Pointer to point to a "
+                        "value of type Result Type"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index 9a4beba..1178ca0 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -346,6 +346,8 @@
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04636"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("ControlBarrier: in Vulkan environment Execution Scope "
                         "is limited to Workgroup and Subgroup"));
 }
@@ -388,9 +390,10 @@
                         "cannot be CrossDevice"));
 }
 
-TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1WorkgroupNonComputeFailure) {
+TEST_F(ValidateBarriers,
+       OpControlBarrierVulkan1p1WorkgroupNonComputeMemoryFailure) {
   const std::string body = R"(
-OpControlBarrier %workgroup %workgroup %acquire
+OpControlBarrier %subgroup %workgroup %acquire
 )";
 
   CompileSuccessfully(GenerateVulkanVertexShaderCode(body), SPV_ENV_VULKAN_1_1);
@@ -402,7 +405,23 @@
                         "and GLCompute execution model"));
 }
 
-TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1WorkgroupNonComputeSuccess) {
+TEST_F(ValidateBarriers,
+       OpControlBarrierVulkan1p1WorkgroupNonComputeExecutionFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %subgroup %acquire
+)";
+
+  CompileSuccessfully(GenerateVulkanVertexShaderCode(body), SPV_ENV_VULKAN_1_1);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04637"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("in Vulkan environment, Workgroup execution scope is "
+                        "only for TaskNV, MeshNV, TessellationControl, and "
+                        "GLCompute execution models"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1WorkgroupComputeSuccess) {
   const std::string body = R"(
 OpControlBarrier %workgroup %workgroup %acquire
 )";
@@ -411,6 +430,39 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
 }
 
+TEST_F(ValidateBarriers, OpControlBarrierVulkan1p1WorkgroupNonComputeSuccess) {
+  const std::string body = R"(
+OpControlBarrier %subgroup %subgroup %acquire
+)";
+
+  CompileSuccessfully(GenerateVulkanVertexShaderCode(body), SPV_ENV_VULKAN_1_1);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierVulkanInvocationSuccess) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %invocation %none
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierVulkanInvocationFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %invocation %acquire
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04641"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ControlBarrier: Vulkan specification requires Memory "
+                "Semantics to be None if used with Invocation Memory Scope"));
+}
+
 TEST_F(ValidateBarriers, OpControlBarrierAcquireAndRelease) {
   const std::string body = R"(
 OpControlBarrier %device %device %acquire_and_release_uniform
@@ -459,9 +511,13 @@
                       SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpControlBarrier execution scope must be Subgroup for "
-                        "Fragment, Vertex, Geometry and TessellationEvaluation "
-                        "execution models"));
+              AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpControlBarrier execution scope must be Subgroup for Fragment, "
+          "Vertex, Geometry, TessellationEvaluation, RayGeneration, "
+          "Intersection, AnyHit, ClosestHit, and Miss execution models"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionFragment1p0) {
@@ -497,9 +553,13 @@
                       SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpControlBarrier execution scope must be Subgroup for "
-                        "Fragment, Vertex, Geometry and TessellationEvaluation "
-                        "execution models"));
+              AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpControlBarrier execution scope must be Subgroup for Fragment, "
+          "Vertex, Geometry, TessellationEvaluation, RayGeneration, "
+          "Intersection, AnyHit, ClosestHit, and Miss execution models"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionVertex1p0) {
@@ -537,9 +597,13 @@
       SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpControlBarrier execution scope must be Subgroup for "
-                        "Fragment, Vertex, Geometry and TessellationEvaluation "
-                        "execution models"));
+              AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpControlBarrier execution scope must be Subgroup for Fragment, "
+          "Vertex, Geometry, TessellationEvaluation, RayGeneration, "
+          "Intersection, AnyHit, ClosestHit, and Miss execution models"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionGeometry1p0) {
@@ -580,9 +644,13 @@
                       SPV_ENV_VULKAN_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpControlBarrier execution scope must be Subgroup for "
-                        "Fragment, Vertex, Geometry and TessellationEvaluation "
-                        "execution models"));
+              AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04682"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpControlBarrier execution scope must be Subgroup for Fragment, "
+          "Vertex, Geometry, TessellationEvaluation, RayGeneration, "
+          "Intersection, AnyHit, ClosestHit, and Miss execution models"));
 }
 
 TEST_F(ValidateBarriers,
@@ -721,7 +789,7 @@
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04649"));
+              AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04732"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("MemoryBarrier: Vulkan specification requires Memory Semantics "
@@ -737,7 +805,7 @@
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04649"));
+              AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04733"));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("MemoryBarrier: expected Memory Semantics to include a "
                         "Vulkan-supported storage class"));
@@ -751,7 +819,7 @@
   CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04649"));
+              AnyVUID("VUID-StandaloneSpirv-OpMemoryBarrier-04733"));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("MemoryBarrier: expected Memory Semantics to include a "
                         "Vulkan-supported storage class"));
@@ -1464,6 +1532,8 @@
 
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04636"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("in Vulkan environment Execution Scope is limited to "
                         "Workgroup and Subgroup"));
 }
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index bbcdbb1..dff9adf 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -767,18 +767,18 @@
 INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotGLCompute,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(
-        Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
-               "WorkgroupId"),
-        Values("Vertex", "Fragment", "Geometry", "TessellationControl",
-               "TessellationEvaluation"),
-        Values("Input"), Values("%u32vec3"),
-        Values("VUID-GlobalInvocationId-GlobalInvocationId-04236 "
-               "VUID-LocalInvocationId-LocalInvocationId-04281 "
-               "VUID-NumWorkgroups-NumWorkgroups-04296 "
-               "VUID-WorkgroupId-WorkgroupId-04422"),
-        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("VUID-GlobalInvocationId-GlobalInvocationId-04236 "
+                   "VUID-LocalInvocationId-LocalInvocationId-04281 "
+                   "VUID-NumWorkgroups-NumWorkgroups-04296 "
+                   "VUID-WorkgroupId-WorkgroupId-04422"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with GLCompute, MeshNV, or "
+                              "TaskNV execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3NotInput,
@@ -2828,9 +2828,10 @@
 
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Vulkan spec allows BuiltIn WorkgroupSize to be used "
-                        "only with GLCompute execution model"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Vulkan spec allows BuiltIn WorkgroupSize to be used "
+                "only with GLCompute, MeshNV, or TaskNV execution model"));
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("is referencing ID <2> (OpConstantComposite) which is "
                         "decorated with BuiltIn WorkgroupSize in function <1> "
@@ -2860,9 +2861,9 @@
 
   CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("BuiltIns can only target variables, structs or constants"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("BuiltIns can only target variables, structure "
+                        "members or constants"));
 }
 
 CodeGenerator GetWorkgroupSizeNotVectorGenerator() {
@@ -3481,35 +3482,6 @@
   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
-                      OpDecorate %12 Location 0
-                      OpDecorate %17 Location 0
-              %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());
@@ -3658,23 +3630,25 @@
 
 INSTANTIATE_TEST_SUITE_P(
     SubgroupInvocationIdAndSizeNotU32, ValidateVulkanSubgroupBuiltIns,
-    Combine(Values("SubgroupLocalInvocationId", "SubgroupSize"),
-            Values("GLCompute"), Values("Input"), Values("%f32"),
-            Values("VUID-SubgroupLocalInvocationId-SubgroupLocalInvocationId-"
-                   "04381 VUID-SubgroupSize-SubgroupSize-04383"),
-            Values(TestResult(SPV_ERROR_INVALID_DATA,
-                              "needs to be a 32-bit int"))));
+    Combine(
+        Values("SubgroupLocalInvocationId", "SubgroupSize"),
+        Values("GLCompute"), Values("Input"), Values("%f32"),
+        Values("VUID-SubgroupLocalInvocationId-SubgroupLocalInvocationId-04381 "
+               "VUID-SubgroupSize-SubgroupSize-04383"),
+        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("VUID-SubgroupLocalInvocationId-SubgroupLocalInvocationId-"
-                   "04380 VUID-SubgroupSize-SubgroupSize-04382"),
-            Values(TestResult(
-                SPV_ERROR_INVALID_DATA,
-                "to be only used for variables with Input storage class"))));
+    Combine(
+        Values("SubgroupLocalInvocationId", "SubgroupSize"),
+        Values("GLCompute"), Values("Output", "Workgroup", "Private"),
+        Values("%u32"),
+        Values("VUID-SubgroupLocalInvocationId-SubgroupLocalInvocationId-04380 "
+               "VUID-SubgroupSize-SubgroupSize-04382"),
+        Values(TestResult(
+            SPV_ERROR_INVALID_DATA,
+            "to be only used for variables with Input storage class"))));
 
 INSTANTIATE_TEST_SUITE_P(
     SubgroupInvocationIdAndSizeOk, ValidateVulkanSubgroupBuiltIns,
@@ -3709,13 +3683,13 @@
 
 INSTANTIATE_TEST_SUITE_P(
     SubgroupNumAndIdNotCompute, ValidateVulkanSubgroupBuiltIns,
-    Combine(
-        Values("SubgroupId", "NumSubgroups"), Values("Vertex"), Values("Input"),
-        Values("%u32"),
-        Values("VUID-SubgroupId-SubgroupId-04367 "
-               "VUID-NumSubgroups-NumSubgroups-04293"),
-        Values(TestResult(SPV_ERROR_INVALID_DATA,
-                          "to be used only with GLCompute execution model"))));
+    Combine(Values("SubgroupId", "NumSubgroups"), Values("Vertex"),
+            Values("Input"), Values("%u32"),
+            Values("VUID-SubgroupId-SubgroupId-04367 "
+                   "VUID-NumSubgroups-NumSubgroups-04293"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with GLCompute, MeshNV, or "
+                              "TaskNV execution model"))));
 
 INSTANTIATE_TEST_SUITE_P(
     SubgroupNumAndIdNotU32, ValidateVulkanSubgroupBuiltIns,
@@ -3778,9 +3752,9 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("BuiltIns can only target variables, structs or constants"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("BuiltIns can only target variables, structure members "
+                        "or constants"));
 }
 
 TEST_F(ValidateBuiltIns, TargetIsVariable) {
@@ -3798,47 +3772,6 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateBuiltIns, TargetIsStruct) {
-  const std::string text = R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-OpDecorate %struct BuiltIn Position
-%struct = OpTypeStruct
-)";
-
-  CompileSuccessfully(text);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateBuiltIns, TargetIsConstant) {
-  const std::string text = R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-OpDecorate %int0 BuiltIn Position
-%int = OpTypeInt 32 0
-%int0 = OpConstant %int 0
-)";
-
-  CompileSuccessfully(text);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateBuiltIns, TargetIsSpecConstant) {
-  const std::string text = R"(
-OpCapability Shader
-OpCapability Linkage
-OpMemoryModel Logical GLSL450
-OpDecorate %int0 BuiltIn Position
-%int = OpTypeInt 32 0
-%int0 = OpSpecConstant %int 0
-)";
-
-  CompileSuccessfully(text);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
 INSTANTIATE_TEST_SUITE_P(
     PrimitiveShadingRateOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index 82f8d38..ffd9e0a 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -1205,8 +1205,10 @@
                             Values(
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt RelaxedPrecision\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var RelaxedPrecision\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Private %intt\n"
+          "%var = OpVariable %ptr Private\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           // Block applies to struct type.
@@ -1224,93 +1226,125 @@
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt RowMajor\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpMemberDecorate %structt 0 RowMajor\n"
+          "%floatt = OpTypeFloat 32\n"
+          "%float2 = OpTypeVector %floatt 2\n"
+          "%mat2x2 = OpTypeMatrix %float2 2\n"
+          "%structt = OpTypeStruct %mat2x2\n" + std::string(kVoidFVoid),
           MatrixDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt ColMajor\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpMemberDecorate %structt 0 ColMajor\n"
+          "%floatt = OpTypeFloat 32\n"
+          "%float2 = OpTypeVector %floatt 2\n"
+          "%mat2x2 = OpTypeMatrix %float2 2\n"
+          "%structt = OpTypeStruct %mat2x2\n" + std::string(kVoidFVoid),
           MatrixDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt ArrayStride 1\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %array ArrayStride 4\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%array = OpTypeRuntimeArray %intt\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt MatrixStride 1\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpMemberDecorate %structt 0 MatrixStride 8\n"
+          "%floatt = OpTypeFloat 32\n"
+          "%float2 = OpTypeVector %floatt 2\n"
+          "%mat2x2 = OpTypeMatrix %float2 2\n"
+          "%structt = OpTypeStruct %mat2x2\n" + std::string(kVoidFVoid),
           MatrixDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt GLSLShared\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %struct GLSLShared\n"
+          "%struct = OpTypeStruct\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt GLSLPacked\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %struct GLSLPacked\n"
+          "%struct = OpTypeStruct\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n"
-          "OpDecorate %intt CPacked\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %struct CPacked\n"
+          "%struct = OpTypeStruct\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt NoPerspective\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var NoPerspective\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"
-          "OpDecorate %intt Flat\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Flat\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"
-          "OpDecorate %intt Patch\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Patch\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           TessellationDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Centroid\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Centroid\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"
-          "OpDecorate %intt Sample\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Sample\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"SampleRateShading"}),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Invariant\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Invariant\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"
-          "OpDecorate %intt Restrict\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Restrict\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Aliased\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Aliased\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Volatile\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Volatile\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n"
-          "OpDecorate %intt Constant\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Constant\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Coherent\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Coherent\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           // NonWritable must target something valid, such as a storage image.
@@ -1324,8 +1358,12 @@
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt NonReadable\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var NonReadable "
+          "%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) +
           // Uniform must target a non-void value.
@@ -1342,8 +1380,10 @@
           KernelDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Stream 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Stream 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Output %intt\n"
+          "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"GeometryStreams"}),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
@@ -1360,33 +1400,44 @@
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Index 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Index 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"
-          "OpDecorate %intt Binding 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var Binding 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Uniform %intt\n"
+          "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt DescriptorSet 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var DescriptorSet 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Uniform %intt\n"
+          "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt Offset 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpMemberDecorate %structt 0 Offset 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%structt = OpTypeStruct %intt\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt XfbBuffer 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var XfbBuffer 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Uniform %intt\n"
+          "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"TransformFeedback"}),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt XfbStride 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var XfbStride 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer Uniform %intt\n"
+          "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"TransformFeedback"}),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n"
@@ -1410,8 +1461,10 @@
           ShaderDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n"
-          "OpDecorate %intt InputAttachmentIndex 0\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpDecorate %var InputAttachmentIndex 0\n"
+          "%intt = OpTypeInt 32 0\n"
+          "%ptr = OpTypePointer UniformConstant %intt\n"
+          "%var = OpVariable %ptr UniformConstant\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"InputAttachment"}),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n"
@@ -1471,264 +1524,308 @@
                             Values(
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn Position\n"
+          "OpDecorate %var BuiltIn Position\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           ShaderDependencies()),
 // Just mentioning PointSize, ClipDistance, or CullDistance as a BuiltIn does
 // not trigger the requirement for the associated capability.
 // See https://github.com/KhronosGroup/SPIRV-Tools/issues/365
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn PointSize\n"
+          "OpDecorate %var BuiltIn PointSize\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn ClipDistance\n"
+          "OpDecorate %var BuiltIn ClipDistance\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn CullDistance\n"
+          "OpDecorate %var BuiltIn CullDistance\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn VertexId\n"
+          "OpDecorate %var BuiltIn VertexId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn InstanceId\n"
+          "OpDecorate %var BuiltIn InstanceId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn PrimitiveId\n"
+          "OpDecorate %var BuiltIn PrimitiveId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           GeometryTessellationDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn InvocationId\n"
+          "OpDecorate %var BuiltIn InvocationId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           GeometryTessellationDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn Layer\n"
+          "OpDecorate %var BuiltIn Layer\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           GeometryDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn ViewportIndex\n"
+          "OpDecorate %var BuiltIn ViewportIndex\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"MultiViewport"}),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn TessLevelOuter\n"
+          "OpDecorate %var BuiltIn TessLevelOuter\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           TessellationDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn TessLevelInner\n"
+          "OpDecorate %var BuiltIn TessLevelInner\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           TessellationDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn TessCoord\n"
+          "OpDecorate %var BuiltIn TessCoord\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           TessellationDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn PatchVertices\n"
+          "OpDecorate %var BuiltIn PatchVertices\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           TessellationDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn FragCoord\n"
+          "OpDecorate %var BuiltIn FragCoord\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn PointCoord\n"
+          "OpDecorate %var BuiltIn PointCoord\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn FrontFacing\n"
+          "OpDecorate %var BuiltIn FrontFacing\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn SampleId\n"
+          "OpDecorate %var BuiltIn SampleId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"SampleRateShading"}),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn SamplePosition\n"
+          "OpDecorate %var BuiltIn SamplePosition\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           std::vector<std::string>{"SampleRateShading"}),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn SampleMask\n"
+          "OpDecorate %var BuiltIn SampleMask\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn FragDepth\n"
+          "OpDecorate %var BuiltIn FragDepth\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn HelperInvocation\n"
+          "OpDecorate %var BuiltIn HelperInvocation\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn VertexIndex\n"
+          "OpDecorate %var BuiltIn VertexIndex\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn InstanceIndex\n"
+          "OpDecorate %var BuiltIn InstanceIndex\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn NumWorkgroups\n"
+          "OpDecorate %var BuiltIn NumWorkgroups\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn WorkgroupSize\n"
+          "OpDecorate %ones BuiltIn WorkgroupSize\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%int3 = OpTypeVector %intt 3\n"
+          "%int_1 = OpConstant %intt 1\n"
+          "%ones = OpConstantComposite %int3 %int_1 %int_1 %int_1\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn WorkgroupId\n"
+          "OpDecorate %var BuiltIn WorkgroupId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn LocalInvocationId\n"
+          "OpDecorate %var BuiltIn LocalInvocationId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn GlobalInvocationId\n"
+          "OpDecorate %var BuiltIn GlobalInvocationId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn LocalInvocationIndex\n"
+          "OpDecorate %var BuiltIn LocalInvocationIndex\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           AllCapabilities()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn WorkDim\n"
+          "OpDecorate %var BuiltIn WorkDim\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn GlobalSize\n"
+          "OpDecorate %var BuiltIn GlobalSize\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn EnqueuedWorkgroupSize\n"
+          "OpDecorate %var BuiltIn EnqueuedWorkgroupSize\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn GlobalOffset\n"
+          "OpDecorate %var BuiltIn GlobalOffset\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn GlobalLinearId\n"
+          "OpDecorate %var BuiltIn GlobalLinearId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn SubgroupSize\n"
+          "OpDecorate %var BuiltIn SubgroupSize\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelAndGroupNonUniformDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn SubgroupMaxSize\n"
+          "OpDecorate %var BuiltIn SubgroupMaxSize\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn NumSubgroups\n"
+          "OpDecorate %var BuiltIn NumSubgroups\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelAndGroupNonUniformDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn NumEnqueuedSubgroups\n"
+          "OpDecorate %var BuiltIn NumEnqueuedSubgroups\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn SubgroupId\n"
+          "OpDecorate %var BuiltIn SubgroupId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelAndGroupNonUniformDependencies()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
           "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn SubgroupLocalInvocationId\n"
+          "OpDecorate %var BuiltIn SubgroupLocalInvocationId\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           KernelAndGroupNonUniformDependencies()),
 std::make_pair(std::string(kOpenCLMemoryModel) +
           "OpEntryPoint Kernel %func \"compute\" \n" +
-          "OpDecorate %int0 BuiltIn VertexIndex\n"
+          "OpDecorate %var BuiltIn VertexIndex\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%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" +
-          "OpDecorate %int0 BuiltIn InstanceIndex\n"
+          "OpDecorate %var BuiltIn InstanceIndex\n"
           "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "%ptr = OpTypePointer Input %intt\n"
+          "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid),
           ShaderDependencies())
 )));
 
@@ -1742,11 +1839,11 @@
                             ValuesIn(AllSpirV10Capabilities()),
                             Values(
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n"
-          "OpMemberDecorate %block 0 BuiltIn PointSize\n"
-          "%f32 = OpTypeFloat 32\n"
-          "%block = OpTypeStruct %f32\n"
-          "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn PointSize\n"
+          "%float = OpTypeFloat 32\n"
+          "%ptr_output_float = OpTypePointer Output %float\n"
+          "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid),
           // Capabilities which should succeed.
           AllVulkan10Capabilities()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
@@ -1775,22 +1872,31 @@
                             ValuesIn(AllSpirV10Capabilities()),
                             Values(
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn PointSize\n"
-          "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn PointSize\n"
+          "%float = OpTypeFloat 32\n"
+          "%ptr_output_float = OpTypePointer Output %float\n"
+          "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid),
           AllSpirV10Capabilities()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn ClipDistance\n"
-          "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn ClipDistance\n"
+          "%float = OpTypeFloat 32\n"
+          "%int = OpTypeInt 32 0\n"
+          "%int_1 = OpConstant %int 1\n"
+          "%array = OpTypeArray %float %int_1\n"
+          "%ptr = OpTypePointer Output %array\n"
+          "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid),
           AllSpirV10Capabilities()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn CullDistance\n"
-          "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn CullDistance\n"
+          "%float = OpTypeFloat 32\n"
+          "%int = OpTypeInt 32 0\n"
+          "%int_1 = OpConstant %int 1\n"
+          "%array = OpTypeArray %float %int_1\n"
+          "%ptr = OpTypePointer Output %array\n"
+          "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid),
           AllSpirV10Capabilities())
 )));
 
@@ -1800,16 +1906,21 @@
                             ValuesIn(AllCapabilities()),
                             Values(
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn PointSize\n"
-          "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn PointSize\n"
+          "%float = OpTypeFloat 32\n"
+          "%ptr_output_float = OpTypePointer Output %float\n"
+          "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid),
           AllVulkan11Capabilities()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn CullDistance\n"
-          "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn CullDistance\n"
+          "%float = OpTypeFloat 32\n"
+          "%int = OpTypeInt 32 0\n"
+          "%int_1 = OpConstant %int 1\n"
+          "%array = OpTypeArray %float %int_1\n"
+          "%ptr = OpTypePointer Output %array\n"
+          "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid),
           AllVulkan11Capabilities())
 )));
 
@@ -1819,16 +1930,21 @@
                             ValuesIn(AllSpirV15Capabilities()),
                             Values(
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn PointSize\n"
-          "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn PointSize\n"
+          "%float = OpTypeFloat 32\n"
+          "%ptr_output_float = OpTypePointer Output %float\n"
+          "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid),
           AllVulkan12Capabilities()),
 std::make_pair(std::string(kGLSL450MemoryModel) +
-          "OpEntryPoint Vertex %func \"shader\" \n" +
-          "OpDecorate %int0 BuiltIn CullDistance\n"
-          "%intt = OpTypeInt 32 0\n"
-          "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid),
+          "OpEntryPoint Vertex %func \"shader\" %var\n" +
+          "OpDecorate %var BuiltIn CullDistance\n"
+          "%float = OpTypeFloat 32\n"
+          "%int = OpTypeInt 32 0\n"
+          "%int_1 = OpConstant %int 1\n"
+          "%array = OpTypeArray %float %int_1\n"
+          "%ptr = OpTypePointer Output %array\n"
+          "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid),
           AllVulkan12Capabilities())
 )));
 
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 9698fb1..311cfa7 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -3520,7 +3520,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
 }
 
 TEST_F(ValidateCFG, MissingMergeSwitchBad2) {
@@ -3544,7 +3547,10 @@
 
   CompileSuccessfully(text);
   EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Selection must be structured"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
 }
 
 TEST_F(ValidateCFG, MissingMergeOneBranchToMergeGood) {
@@ -3594,7 +3600,7 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateCFG, MissingMergeOneTargetSwitchGood) {
+TEST_F(ValidateCFG, MissingMergeOneTargetSwitchBad) {
   const std::string text = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -3612,10 +3618,14 @@
 )";
 
   CompileSuccessfully(text);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
 }
 
-TEST_F(ValidateCFG, MissingMergeOneUnseenTargetSwitchGood) {
+TEST_F(ValidateCFG, MissingMergeOneUnseenTargetSwitchBad) {
   const std::string text = R"(
 OpCapability Shader
 OpCapability Linkage
@@ -3640,7 +3650,11 @@
 )";
 
   CompileSuccessfully(text);
-  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "OpSwitch must be preceeded by an OpSelectionMerge instruction"));
 }
 
 TEST_F(ValidateCFG, MissingMergeLoopBreakGood) {
@@ -4215,6 +4229,67 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateCFG, StructuredSelections_RegisterBothTrueAndFalse) {
+  // In this test, we try to make a case where the false branches
+  // to %20 and %60 from blocks %10 and %50 must be registered
+  // during the validity check for sturctured selections.
+  // However, an error is caught earlier in the flow, that the
+  // branches from %100 to %20 and %60 violate dominance.
+  const std::string text = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint Fragment %main "main"
+    OpExecutionMode %main OriginUpperLeft
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %cond = OpUndef %bool
+
+    %main = OpFunction %void None %void_fn
+
+    %1 = OpLabel
+    OpSelectionMerge %999 None
+    OpBranchConditional %cond %10 %100
+
+    %10 = OpLabel
+    OpSelectionMerge %30 None  ; force registration of %30
+    OpBranchConditional %cond %30 %20 ; %20 should be registered too
+
+    %20 = OpLabel
+    OpBranch %30
+
+    %30 = OpLabel ; merge for first if
+    OpBranch %50
+
+
+    %50 = OpLabel
+    OpSelectionMerge %70 None  ; force registration of %70
+    OpBranchConditional %cond %70 %60 ; %60 should be registered
+
+    %60 = OpLabel
+    OpBranch %70
+
+    %70 = OpLabel ; merge for second if
+    OpBranch %999
+
+    %100 = OpLabel
+    OpBranchConditional %cond %20 %60 ; should require a merge
+
+    %999 = OpLabel
+    OpReturn
+
+    OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("The selection construct with the selection header "
+                        "8[%8] does not dominate the merge block 10[%10]\n"));
+}
+
 TEST_F(ValidateCFG, UnreachableIsStaticallyReachable) {
   const std::string text = R"(
 OpCapability Shader
@@ -4350,6 +4425,160 @@
               HasSubstr("OpPhi must not have void result type"));
 }
 
+TEST_F(ValidateCFG, InvalidExitSingleBlockLoop) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %5 "BAD"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%fn = OpFunction %void None %void_fn
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranchConditional %undef %3 %5
+%5 = OpLabel
+OpLoopMerge %6 %5 None
+OpBranchConditional %undef %5 %4
+%6 = OpLabel
+OpReturn
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("block <ID> 1[%BAD] exits the continue headed by <ID> "
+                        "1[%BAD], but not via a structured exit"));
+}
+
+TEST_F(ValidateCFG, SwitchSelectorNotAnInt) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSelectionMerge %default None
+OpSwitch %float_1 %default
+%default = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Selector type must be OpTypeInt"));
+}
+
+TEST_F(ValidateCFG, SwitchDefaultNotALabel) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_1 = OpConstant %int 1
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpSelectionMerge %default None
+OpSwitch %int_1 %int_1
+%default = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Default must be an OpLabel instruction"));
+}
+
+TEST_F(ValidateCFG, BlockDepthRecursion) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranchConditional %undef %3 %4
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpBranch %5
+%5 = OpLabel
+OpSelectionMerge %2 None
+OpBranchConditional %undef %6 %7
+%6 = OpLabel
+OpReturn
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, BadStructuredExitBackwardsMerge) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%undef = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %4 %5 None
+OpBranchConditional %undef %4 %6
+%6 = OpLabel
+OpSelectionMerge %7 None
+OpBranchConditional %undef %8 %9
+%7 = OpLabel
+OpReturn
+%8 = OpLabel
+OpBranch %5
+%9 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %undef %5 %5
+%5 = OpLabel
+OpBranch %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_composites_test.cpp b/test/val/val_composites_test.cpp
index bf7caa9..507ee88 100644
--- a/test/val/val_composites_test.cpp
+++ b/test/val/val_composites_test.cpp
@@ -86,10 +86,10 @@
 %f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12
 
 %f32vec2arr3 = OpTypeArray %f32vec2 %u32_3
-%f32vec2rarr = OpTypeRuntimeArray %f32vec2
+%f32vec2arr2 = OpTypeArray %f32vec2 %u32_2
 
 %f32u32struct = OpTypeStruct %f32 %u32
-%big_struct = OpTypeStruct %f32 %f32vec4 %f32mat23 %f32vec2arr3 %f32vec2rarr %f32u32struct
+%big_struct = OpTypeStruct %f32 %f32vec4 %f32mat23 %f32vec2arr3 %f32vec2arr2 %f32u32struct
 
 %ptr_big_struct = OpTypePointer Uniform %big_struct
 %var_big_struct = OpVariable %ptr_big_struct Uniform
@@ -150,7 +150,6 @@
 ; uniform blockName {
 ;   S s;
 ;   bool cond;
-;   RunTimeArray arr;
 ; }
 
 %f32arr = OpTypeRuntimeArray %float
@@ -161,7 +160,7 @@
 %_ptr_Function_vec4 = OpTypePointer Function %v4float
 %_ptr_Uniform_vec4 = OpTypePointer Uniform %v4float
 %struct_s = OpTypeStruct %int %array5_vec4 %int %array5_mat4x3
-%struct_blockName = OpTypeStruct %struct_s %int %f32arr
+%struct_blockName = OpTypeStruct %struct_s %int
 %_ptr_Uniform_blockName = OpTypePointer Uniform %struct_blockName
 %_ptr_Uniform_struct_s = OpTypePointer Uniform %struct_s
 %_ptr_Uniform_array5_mat4x3 = OpTypePointer Uniform %array5_mat4x3
@@ -644,8 +643,8 @@
 %val12 = OpCompositeExtract %f32 %struct 2 2 1
 %val13 = OpCompositeExtract %f32vec2 %struct 3 2
 %val14 = OpCompositeExtract %f32 %struct 3 2 1
-%val15 = OpCompositeExtract %f32vec2 %struct 4 100
-%val16 = OpCompositeExtract %f32 %struct 4 1000 1
+%val15 = OpCompositeExtract %f32vec2 %struct 4 1
+%val16 = OpCompositeExtract %f32 %struct 4 0 1
 %val17 = OpCompositeExtract %f32 %struct 5 0
 %val18 = OpCompositeExtract %u32 %struct 5 1
 )";
@@ -868,8 +867,8 @@
 %val12 = OpCompositeInsert %big_struct %f32_3 %struct 2 2 1
 %val13 = OpCompositeInsert %big_struct %f32vec2_01 %struct 3 2
 %val14 = OpCompositeInsert %big_struct %f32_3 %struct 3 2 1
-%val15 = OpCompositeInsert %big_struct %f32vec2_01 %struct 4 100
-%val16 = OpCompositeInsert %big_struct %f32_3 %struct 4 1000 1
+%val15 = OpCompositeInsert %big_struct %f32vec2_01 %struct 4 1
+%val16 = OpCompositeInsert %big_struct %f32_3 %struct 4 0 1
 %val17 = OpCompositeInsert %big_struct %f32_3 %struct 5 0
 %val18 = OpCompositeInsert %big_struct %u32_3 %struct 5 1
 )";
@@ -1382,8 +1381,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Index is out of bounds, can not find index 3 in the "
-                        "structure <id> '25'. This structure has 3 members. "
-                        "Largest valid index is 2."));
+                        "structure <id> '25'. This structure has 2 members. "
+                        "Largest valid index is 1."));
 }
 
 // Invalid. Index into a struct is larger than the number of struct members.
@@ -1403,8 +1402,8 @@
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("Index is out of bounds, can not find index 3 in the structure "
-                "<id> '25'. This structure has 3 members. Largest valid index "
-                "is 2."));
+                "<id> '25'. This structure has 2 members. Largest valid index "
+                "is 1."));
 }
 
 // #1403: Ensure that the default spec constant value is not used to check the
diff --git a/test/val/val_conversion_test.cpp b/test/val/val_conversion_test.cpp
index 47e6793..b9802ec 100644
--- a/test/val/val_conversion_test.cpp
+++ b/test/val/val_conversion_test.cpp
@@ -1464,16 +1464,16 @@
 
 TEST_F(ValidateConversion, ConvertUToPtrPSBSuccess) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 %uint64 = OpTypeInt 64 0
 %u64_1 = OpConstant %uint64 1
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
 %main = OpFunction %void None %voidfn
@@ -1489,11 +1489,11 @@
 
 TEST_F(ValidateConversion, ConvertUToPtrPSBStorageClass) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 %uint64 = OpTypeInt 64 0
@@ -1513,22 +1513,54 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Pointer storage class must be "
-                        "PhysicalStorageBufferEXT: ConvertUToPtr"));
+                        "PhysicalStorageBuffer: ConvertUToPtr"));
+}
+
+TEST_F(ValidateConversion, ConvertUToPtrVulkanWrongWidth) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddresses
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%uint32 = OpTypeInt 32 0
+%uint64 = OpTypeInt 64 0
+%u32_1 = OpConstant %uint32 1
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpConvertUToPtr %ptr %u32_1
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04710"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("PhysicalStorageBuffer64 addressing mode requires the input "
+                "integer to have a 64-bit width for Vulkan environment."));
 }
 
 TEST_F(ValidateConversion, ConvertPtrToUPSBSuccess) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 OpDecorate %val1 RestrictPointerEXT
 %uint64 = OpTypeInt 64 0
 %u64_1 = OpConstant %uint64 1
-%ptr = OpTypePointer PhysicalStorageBufferEXT %uint64
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
 %pptr_f = OpTypePointer Function %ptr
 %void = OpTypeVoid
 %voidfn = OpTypeFunction %void
@@ -1547,11 +1579,11 @@
 
 TEST_F(ValidateConversion, ConvertPtrToUPSBStorageClass) {
   const std::string body = R"(
-OpCapability PhysicalStorageBufferAddressesEXT
+OpCapability PhysicalStorageBufferAddresses
 OpCapability Int64
 OpCapability Shader
 OpExtension "SPV_EXT_physical_storage_buffer"
-OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
 OpEntryPoint Fragment %main "main"
 OpExecutionMode %main OriginUpperLeft
 %uint64 = OpTypeInt 64 0
@@ -1571,7 +1603,42 @@
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Pointer storage class must be "
-                        "PhysicalStorageBufferEXT: ConvertPtrToU"));
+                        "PhysicalStorageBuffer: ConvertPtrToU"));
+}
+
+TEST_F(ValidateConversion, ConvertPtrToUVulkanWrongWidth) {
+  const std::string body = R"(
+OpCapability PhysicalStorageBufferAddresses
+OpCapability Int64
+OpCapability Shader
+OpExtension "SPV_EXT_physical_storage_buffer"
+OpMemoryModel PhysicalStorageBuffer64 GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %val1 RestrictPointerEXT
+%uint32 = OpTypeInt 32 0
+%uint64 = OpTypeInt 64 0
+%ptr = OpTypePointer PhysicalStorageBuffer %uint64
+%pptr_f = OpTypePointer Function %ptr
+%void = OpTypeVoid
+%voidfn = OpTypeFunction %void
+%main = OpFunction %void None %voidfn
+%entry = OpLabel
+%val1 = OpVariable %pptr_f Function
+%val2 = OpLoad %ptr %val1
+%val3 = OpConvertPtrToU %uint32 %val2
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(body.c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04710"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("PhysicalStorageBuffer64 addressing mode requires the result "
+                "integer type to have a 64-bit width for Vulkan environment."));
 }
 
 using ValidateSmallConversions = spvtest::ValidateBase<std::string>;
diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp
index 8fe1aa6..f2953ed 100644
--- a/test/val/val_decoration_test.cpp
+++ b/test/val/val_decoration_test.cpp
@@ -687,8 +687,7 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Block decoration on a non-struct type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type"));
 }
 
 TEST_F(ValidateDecorations, BlockDecoratingIntBad) {
@@ -713,8 +712,7 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Block decoration on a non-struct type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type"));
 }
 
 TEST_F(ValidateDecorations, BlockMissingOffsetBad) {
@@ -4992,6 +4990,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_DATA,
             ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04636"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr(": in Vulkan environment Execution Scope is limited to "
                         "Workgroup and Subgroup"));
 }
@@ -6127,9 +6127,7 @@
   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"));
+              HasSubstr("must be a memory object declaration"));
 }
 
 TEST_F(ValidateDecorations, NonWritableTypeTargetBad) {
@@ -6138,9 +6136,7 @@
   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"));
+              HasSubstr("must be a memory object declaration"));
 }
 
 TEST_F(ValidateDecorations, NonWritableValueTargetBad) {
@@ -6149,9 +6145,7 @@
   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"));
+              HasSubstr("must be a memory object declaration"));
 }
 
 TEST_F(ValidateDecorations, NonWritableValueParamBad) {
@@ -6159,10 +6153,7 @@
 
   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"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a pointer type"));
 }
 
 TEST_F(ValidateDecorations, NonWritablePointerParamButWrongTypeBad) {
@@ -6465,8 +6456,7 @@
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Target of Component decoration must be "
-                        "a memory object declaration"));
+              HasSubstr("must be a memory object declaration"));
 }
 
 TEST_F(ValidateDecorations, ComponentDecorationBadStorageClass) {
@@ -6765,8 +6755,8 @@
 )";
 
   CompileSuccessfully(spirv);
-  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
-  EXPECT_THAT(getDiagnosticString(), Eq(""));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a pointer type"));
 }
 
 TEST_F(ValidateDecorations, VulkanStorageBufferBlock) {
@@ -7162,9 +7152,7 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Location decoration can only be applied to a variable "
-                        "or member of a structure type"));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable"));
 }
 
 TEST_F(ValidateDecorations, LocationFloatBad) {
@@ -7178,9 +7166,675 @@
 
   CompileSuccessfully(spirv);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable"));
+}
+
+TEST_F(ValidateDecorations, WorkgroupSingleBlockVariable) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_
+               OpExecutionMode %main LocalSize 8 1 1
+               OpMemberDecorate %first 0 Offset 0
+               OpDecorate %first Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %first = OpTypeStruct %int
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS,
+	    ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateDecorations, WorkgroupBlockVariableRequiresV14) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_
+               OpExecutionMode %main LocalSize 8 1 1
+               OpMemberDecorate %first 0 Offset 0
+               OpDecorate %first Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %first = OpTypeStruct %int
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
+  EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
+            ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Location decoration can only be applied to a variable "
-                        "or member of a structure type"));
+              HasSubstr("requires SPIR-V version 1.4 or later"));
+}
+
+TEST_F(ValidateDecorations, WorkgroupSingleNonBlockVariable) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %a
+               OpExecutionMode %main LocalSize 8 1 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+          %a = OpVariable %_ptr_Workgroup_int Workgroup
+      %int_2 = OpConstant %int 2
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpStore %a %int_2
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS,
+	    ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateDecorations, WorkgroupMultiBlockVariable) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_ %__0
+               OpExecutionMode %main LocalSize 8 1 1
+               OpMemberDecorate %first 0 Offset 0
+               OpDecorate %first Block
+               OpMemberDecorate %second 0 Offset 0
+               OpDecorate %second Block
+               OpDecorate %_ Aliased
+               OpDecorate %__0 Aliased
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %first = OpTypeStruct %int
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+     %second = OpTypeStruct %int
+%_ptr_Workgroup_second = OpTypePointer Workgroup %second
+        %__0 = OpVariable %_ptr_Workgroup_second Workgroup
+      %int_3 = OpConstant %int 3
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+         %18 = OpAccessChain %_ptr_Workgroup_int %__0 %int_0
+               OpStore %18 %int_3
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS,
+	    ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateDecorations, WorkgroupBlockVariableWith8BitType) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Int8
+               OpCapability WorkgroupMemoryExplicitLayout8BitAccessKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_
+               OpExecutionMode %main LocalSize 2 1 1
+               OpMemberDecorate %first 0 Offset 0
+               OpDecorate %first Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+       %char = OpTypeInt 8 1
+      %first = OpTypeStruct %char
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+     %char_2 = OpConstant %char 2
+%_ptr_Workgroup_char = OpTypePointer Workgroup %char
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %14 = OpAccessChain %_ptr_Workgroup_char %_ %int_0
+               OpStore %14 %char_2
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS,
+	    ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateDecorations, WorkgroupMultiNonBlockVariable) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %a %b
+               OpExecutionMode %main LocalSize 8 1 1
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+          %a = OpVariable %_ptr_Workgroup_int Workgroup
+      %int_2 = OpConstant %int 2
+          %b = OpVariable %_ptr_Workgroup_int Workgroup
+      %int_3 = OpConstant %int 3
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpStore %a %int_2
+               OpStore %b %int_3
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS,
+	    ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateDecorations, WorkgroupBlockVariableWith16BitType) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability Float16
+               OpCapability Int16
+               OpCapability WorkgroupMemoryExplicitLayout16BitAccessKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_
+               OpExecutionMode %main LocalSize 2 1 1
+               OpMemberDecorate %first 0 Offset 0
+               OpMemberDecorate %first 1 Offset 2
+               OpDecorate %first Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %short = OpTypeInt 16 1
+       %half = OpTypeFloat 16
+      %first = OpTypeStruct %short %half
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+    %short_3 = OpConstant %short 3
+%_ptr_Workgroup_short = OpTypePointer Workgroup %short
+      %int_1 = OpConstant %int 1
+%half_0x1_898p_3 = OpConstant %half 0x1.898p+3
+%_ptr_Workgroup_half = OpTypePointer Workgroup %half
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %15 = OpAccessChain %_ptr_Workgroup_short %_ %int_0
+               OpStore %15 %short_3
+         %19 = OpAccessChain %_ptr_Workgroup_half %_ %int_1
+               OpStore %19 %half_0x1_898p_3
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_SUCCESS,
+	    ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+}
+
+TEST_F(ValidateDecorations, WorkgroupBlockVariableScalarLayout) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %B
+               OpSource GLSL 450
+               OpMemberDecorate %S 0 Offset 0
+               OpMemberDecorate %S 1 Offset 4
+               OpMemberDecorate %S 2 Offset 16
+               OpMemberDecorate %S 3 Offset 28
+               OpDecorate %S Block
+               OpDecorate %B Aliased
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+          %S = OpTypeStruct %float %v3float %v3float %v3float
+%_ptr_Workgroup_S = OpTypePointer Workgroup %S
+          %B = OpVariable %_ptr_Workgroup_S Workgroup
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  spvValidatorOptionsSetWorkgroupScalarBlockLayout(getValidatorOptions(), true);
+  EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4))
+      << getDiagnosticString();
+}
+
+TEST_F(ValidateDecorations, WorkgroupMixBlockAndNonBlockBad) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_ %b
+               OpExecutionMode %main LocalSize 8 1 1
+               OpMemberDecorate %first 0 Offset 0
+               OpDecorate %first Block
+               OpDecorate %_ Aliased
+               OpDecorate %b Aliased
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %first = OpTypeStruct %int
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+          %b = OpVariable %_ptr_Workgroup_int Workgroup
+      %int_3 = OpConstant %int 3
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+               OpStore %b %int_3
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("either all or none of the Workgroup Storage Class variables "
+                "in the entry point interface must point to struct types "
+                "decorated with Block"));
+}
+
+TEST_F(ValidateDecorations, WorkgroupMultiBlockVariableMissingAliased) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_ %__0
+               OpExecutionMode %main LocalSize 8 1 1
+               OpMemberDecorate %first 0 Offset 0
+               OpDecorate %first Block
+               OpMemberDecorate %second 0 Offset 0
+               OpDecorate %second Block
+               OpDecorate %_ Aliased
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %first = OpTypeStruct %int
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+     %second = OpTypeStruct %int
+%_ptr_Workgroup_second = OpTypePointer Workgroup %second
+        %__0 = OpVariable %_ptr_Workgroup_second Workgroup
+      %int_3 = OpConstant %int 3
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+         %18 = OpAccessChain %_ptr_Workgroup_int %__0 %int_0
+               OpStore %18 %int_3
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
+            ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("more than one Workgroup Storage Class variable in the "
+                "entry point interface point to a type decorated with Block, "
+                "all of them must be decorated with Aliased"));
+}
+
+TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableNotAStruct) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_
+               OpExecutionMode %main LocalSize 8 1 1
+               OpDecorate %first Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %int_3 = OpConstant %int 3
+      %first = OpTypeArray %int %int_3
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type"));
+}
+
+TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableMissingLayout) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_
+               OpExecutionMode %main LocalSize 8 1 1
+               OpDecorate %first Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %first = OpTypeStruct %int
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Block must be explicitly laid out with Offset decorations"));
+}
+
+TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableBadLayout) {
+  std::string spirv = R"(
+               OpCapability Shader
+               OpCapability WorkgroupMemoryExplicitLayoutKHR
+               OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %_
+               OpExecutionMode %main LocalSize 8 1 1
+               OpMemberDecorate %first 0 Offset 1
+               OpDecorate %first Block
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+        %int = OpTypeInt 32 1
+      %first = OpTypeStruct %int
+%_ptr_Workgroup_first = OpTypePointer Workgroup %first
+          %_ = OpVariable %_ptr_Workgroup_first Workgroup
+      %int_0 = OpConstant %int 0
+      %int_2 = OpConstant %int 2
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+       %main = OpFunction %void None %3
+          %5 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %_ %int_0
+               OpStore %13 %int_2
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID,
+            ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Block for variable in Workgroup storage class must follow "
+          "standard storage buffer layout rules: "
+          "member 0 at offset 1 is not aligned to 4"));
+}
+
+TEST_F(ValidateDecorations, BadMatrixStrideUniform) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 3
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float4 = OpTypeVector %float 4
+%matrix4x4 = OpTypeMatrix %float4 4
+%block = OpTypeStruct %matrix4x4
+%block_ptr = OpTypePointer Uniform %block
+%var = OpVariable %block_ptr Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  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 is "
+          "a matrix with stride 3 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, BadMatrixStrideStorageBuffer) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 3
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float4 = OpTypeVector %float 4
+%matrix4x4 = OpTypeMatrix %float4 4
+%block = OpTypeStruct %matrix4x4
+%block_ptr = OpTypePointer StorageBuffer %block
+%var = OpVariable %block_ptr StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Structure id 2 decorated as Block for variable in StorageBuffer "
+          "storage class must follow standard storage buffer layout rules: "
+          "member 0 is a matrix with stride 3 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, BadMatrixStridePushConstant) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 3
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float4 = OpTypeVector %float 4
+%matrix4x4 = OpTypeMatrix %float4 4
+%block = OpTypeStruct %matrix4x4
+%block_ptr = OpTypePointer PushConstant %block
+%var = OpVariable %block_ptr PushConstant
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Structure id 2 decorated as Block for variable in PushConstant "
+          "storage class must follow standard storage buffer layout rules: "
+          "member 0 is a matrix with stride 3 not satisfying alignment to 16"));
+}
+
+TEST_F(ValidateDecorations, BadMatrixStrideStorageBufferScalarLayout) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 Offset 0
+OpMemberDecorate %block 0 MatrixStride 3
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float4 = OpTypeVector %float 4
+%matrix4x4 = OpTypeMatrix %float4 4
+%block = OpTypeStruct %matrix4x4
+%block_ptr = OpTypePointer StorageBuffer %block
+%var = OpVariable %block_ptr StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  options_->scalar_block_layout = true;
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Structure id 2 decorated as Block for variable in StorageBuffer "
+          "storage class must follow scalar storage buffer layout rules: "
+          "member 0 is a matrix with stride 3 not satisfying alignment to 4"));
+}
+
+TEST_F(ValidateDecorations, MissingOffsetStructNestedInArray) {
+  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 %array ArrayStride 4
+OpDecorate %outer Block
+OpMemberDecorate %outer 0 Offset 0
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_4 = OpConstant %int 4
+%inner = OpTypeStruct %int
+%array = OpTypeArray %inner %int_4
+%outer = OpTypeStruct %array
+%ptr_ssbo_outer = OpTypePointer StorageBuffer %outer
+%var = OpVariable %ptr_ssbo_outer StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Structure id 3 decorated as Block must be explicitly "
+                        "laid out with Offset decorations"));
+}
+
+TEST_F(ValidateDecorations, AllOnesOffset) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %var DescriptorSet 0
+OpDecorate %var Binding 0
+OpDecorate %outer Block
+OpMemberDecorate %outer 0 Offset 0
+OpMemberDecorate %struct 0 Offset 4294967295
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%struct = OpTypeStruct %int
+%outer = OpTypeStruct %struct
+%ptr = OpTypePointer Uniform %outer
+%var = OpVariable %ptr Uniform
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("decorated as Block must be explicitly laid out with "
+                        "Offset decorations"));
 }
 
 }  // namespace
diff --git a/test/val/val_derivatives_test.cpp b/test/val/val_derivatives_test.cpp
index 606abb9..0a84661 100644
--- a/test/val/val_derivatives_test.cpp
+++ b/test/val/val_derivatives_test.cpp
@@ -160,6 +160,31 @@
                         "execution model: DPdx"));
 }
 
+TEST_F(ValidateDerivatives, NoExecutionModeGLCompute) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float4 = OpTypeVector %float 4
+%undef = OpUndef %float4
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%derivative = OpDPdy %float4 %undef
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Derivative instructions require "
+                        "DerivativeGroupQuadsNV or DerivativeGroupLinearNV "
+                        "execution mode for GLCompute execution model"));
+}
+
 using ValidateHalfDerivatives = spvtest::ValidateBase<std::string>;
 
 TEST_P(ValidateHalfDerivatives, ScalarFailure) {
diff --git a/test/val/val_ext_inst_debug_test.cpp b/test/val/val_ext_inst_debug_test.cpp
new file mode 100644
index 0000000..307a800
--- /dev/null
+++ b/test/val/val_ext_inst_debug_test.cpp
@@ -0,0 +1,5313 @@
+// 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 validation rules of GLSL.450.std and OpenCL.std extended instructions.
+// Doesn't test OpenCL.std vector size 2, 3, 4, 8 or 16 rules (not supported
+// by standard SPIR-V).
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+using ValidateOldDebugInfo = spvtest::ValidateBase<std::string>;
+using ValidateOpenCL100DebugInfo = spvtest::ValidateBase<std::string>;
+using ValidateXDebugInfo = spvtest::ValidateBase<std::string>;
+using ValidateLocalDebugInfoOutOfFunction = spvtest::ValidateBase<std::string>;
+using ValidateOpenCL100DebugInfoDebugTypedef =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugTypedef =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeEnum =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugTypeEnum =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeComposite =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugTypeComposite =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeMember =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugTypeMember =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugTypeInheritance =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugFunction =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugFunction =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugFunctionDeclaration =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugFunctionDeclaration =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugLexicalBlock =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugLexicalBlock =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugLocalVariable =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugLocalVariable =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugGlobalVariable =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugGlobalVariable =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugDeclare =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugDeclare =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateOpenCL100DebugInfoDebugValue =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfoDebugValue =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+using ValidateVulkan100DebugInfo = spvtest::ValidateBase<std::string>;
+
+std::string GenerateShaderCodeForDebugInfo(
+    const std::string& op_string_instructions,
+    const std::string& op_const_instructions,
+    const std::string& debug_instructions_before_main, const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& execution_model = "Fragment") {
+  std::ostringstream ss;
+  ss << R"(
+OpCapability Shader
+OpCapability Float16
+OpCapability Float64
+OpCapability Int16
+OpCapability Int64
+)";
+
+  ss << capabilities_and_extensions;
+  ss << "%extinst = OpExtInstImport \"GLSL.std.450\"\n";
+  ss << "OpMemoryModel Logical GLSL450\n";
+  ss << "OpEntryPoint " << execution_model << " %main \"main\""
+     << " %f32_output"
+     << " %f32vec2_output"
+     << " %u32_output"
+     << " %u32vec2_output"
+     << " %u64_output"
+     << " %f32_input"
+     << " %f32vec2_input"
+     << " %u32_input"
+     << " %u32vec2_input"
+     << " %u64_input"
+     << "\n";
+  if (execution_model == "Fragment") {
+    ss << "OpExecutionMode %main OriginUpperLeft\n";
+  }
+
+  ss << op_string_instructions;
+
+  ss << R"(
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%bool = OpTypeBool
+%f16 = OpTypeFloat 16
+%f32 = OpTypeFloat 32
+%f64 = OpTypeFloat 64
+%u32 = OpTypeInt 32 0
+%s32 = OpTypeInt 32 1
+%u64 = OpTypeInt 64 0
+%s64 = OpTypeInt 64 1
+%u16 = OpTypeInt 16 0
+%s16 = OpTypeInt 16 1
+%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
+%s32vec2 = OpTypeVector %s32 2
+%u32vec4 = OpTypeVector %u32 4
+%s32vec4 = OpTypeVector %s32 4
+%u64vec2 = OpTypeVector %u64 2
+%s64vec2 = OpTypeVector %s64 2
+%f64mat22 = OpTypeMatrix %f64vec2 2
+%f32mat22 = OpTypeMatrix %f32vec2 2
+%f32mat23 = OpTypeMatrix %f32vec2 3
+%f32mat32 = OpTypeMatrix %f32vec3 2
+%f32mat33 = OpTypeMatrix %f32vec3 3
+
+%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
+
+%f16_0 = OpConstant %f16 0
+%f16_1 = OpConstant %f16 1
+%f16_h = OpConstant %f16 0.5
+
+%u32_0 = OpConstant %u32 0
+%u32_1 = OpConstant %u32 1
+%u32_2 = OpConstant %u32 2
+%u32_3 = OpConstant %u32 3
+
+%s32_0 = OpConstant %s32 0
+%s32_1 = OpConstant %s32 1
+%s32_2 = OpConstant %s32 2
+%s32_3 = OpConstant %s32 3
+
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+%u64_2 = OpConstant %u64 2
+%u64_3 = OpConstant %u64 3
+
+%s64_0 = OpConstant %s64 0
+%s64_1 = OpConstant %s64 1
+%s64_2 = OpConstant %s64 2
+%s64_3 = OpConstant %s64 3
+)";
+
+  ss << op_const_instructions;
+
+  ss << R"(
+%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
+%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
+
+%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
+%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
+
+%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
+%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
+
+%s64vec2_01 = OpConstantComposite %s64vec2 %s64_0 %s64_1
+%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1
+
+%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12
+%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12
+
+%f32_ptr_output = OpTypePointer Output %f32
+%f32vec2_ptr_output = OpTypePointer Output %f32vec2
+
+%u32_ptr_output = OpTypePointer Output %u32
+%u32vec2_ptr_output = OpTypePointer Output %u32vec2
+
+%u64_ptr_output = OpTypePointer Output %u64
+
+%f32_output = OpVariable %f32_ptr_output Output
+%f32vec2_output = OpVariable %f32vec2_ptr_output Output
+
+%u32_output = OpVariable %u32_ptr_output Output
+%u32vec2_output = OpVariable %u32vec2_ptr_output Output
+
+%u64_output = OpVariable %u64_ptr_output Output
+
+%f32_ptr_input = OpTypePointer Input %f32
+%f32vec2_ptr_input = OpTypePointer Input %f32vec2
+
+%u32_ptr_input = OpTypePointer Input %u32
+%u32vec2_ptr_input = OpTypePointer Input %u32vec2
+
+%u64_ptr_input = OpTypePointer Input %u64
+
+%f32_ptr_function = OpTypePointer Function %f32
+
+%f32_input = OpVariable %f32_ptr_input Input
+%f32vec2_input = OpVariable %f32vec2_ptr_input Input
+
+%u32_input = OpVariable %u32_ptr_input Input
+%u32vec2_input = OpVariable %u32vec2_ptr_input Input
+
+%u64_input = OpVariable %u64_ptr_input Input
+
+%u32_ptr_function = OpTypePointer Function %u32
+
+%struct_f16_u16 = OpTypeStruct %f16 %u16
+%struct_f32_f32 = OpTypeStruct %f32 %f32
+%struct_f32_f32_f32 = OpTypeStruct %f32 %f32 %f32
+%struct_f32_u32 = OpTypeStruct %f32 %u32
+%struct_f32_u32_f32 = OpTypeStruct %f32 %u32 %f32
+%struct_u32_f32 = OpTypeStruct %u32 %f32
+%struct_u32_u32 = OpTypeStruct %u32 %u32
+%struct_f32_f64 = OpTypeStruct %f32 %f64
+%struct_f32vec2_f32vec2 = OpTypeStruct %f32vec2 %f32vec2
+%struct_f32vec2_u32vec2 = OpTypeStruct %f32vec2 %u32vec2
+)";
+
+  ss << debug_instructions_before_main;
+
+  ss << R"(
+%main = OpFunction %void None %func
+%main_entry = OpLabel
+)";
+
+  ss << body;
+
+  ss << R"(
+OpReturn
+OpFunctionEnd)";
+
+  return ss.str();
+}
+
+TEST_F(ValidateOldDebugInfo, UseDebugInstructionOutOfFunction) {
+  const std::string src = R"(
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%cu = OpExtInst %void %DbgExt DebugCompilationUnit %code 1 1
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "DebugInfo"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, UseDebugInstructionOutOfFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugSourceInFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", "", dbg_inst,
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Debug info extension instructions other than DebugScope, "
+                "DebugNoScope, DebugDeclare, DebugValue must appear between "
+                "section 9 (types, constants, global variables) and section 10 "
+                "(function declarations)"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugSourceInFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", "", dbg_inst,
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Debug info extension instructions other than DebugScope, "
+                "DebugNoScope, DebugDeclare, DebugValue must appear between "
+                "section 9 (types, constants, global variables) and section 10 "
+                "(function declarations)"));
+}
+
+TEST_P(ValidateLocalDebugInfoOutOfFunction, OpenCLDebugInfo100DebugScope) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%int_name = OpString "int"
+%foo_name = OpString "foo"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %u32_0 Signed
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %int_info %dbg_src 1 1 %main_info FlagIsLocal
+%expr = OpExtInst %void %DbgExt DebugExpression
+)";
+
+  const std::string body = R"(
+%foo = OpVariable %u32_ptr_function Function
+%foo_val = OpLoad %u32 %foo
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header + GetParam(), body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("DebugScope, DebugNoScope, DebugDeclare, DebugValue "
+                        "of debug info extension must appear in a function "
+                        "body"));
+}
+
+TEST_P(ValidateLocalDebugInfoOutOfFunction, VulkanDebugInfo100DebugScope) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%int_name = OpString "int"
+%foo_name = OpString "foo"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %u32_0 %u32_1 %u32_0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_1 %u32_1 %comp_unit %main_linkage_name %u32_3 %u32_1
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %int_info %dbg_src %u32_1 %u32_1 %main_info %u32_4
+%expr = OpExtInst %void %DbgExt DebugExpression
+)";
+
+  const std::string body = R"(
+%foo = OpVariable %u32_ptr_function Function
+%main_def = OpExtInst %void %DbgExt DebugFunctionDefinition %main_info %main
+%foo_val = OpLoad %u32 %foo
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header + GetParam(), body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("DebugScope, DebugNoScope, DebugDeclare, DebugValue "
+                        "of debug info extension must appear in a function "
+                        "body"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllLocalDebugInfo, ValidateLocalDebugInfoOutOfFunction,
+    ::testing::ValuesIn(std::vector<std::string>{
+        "%main_scope = OpExtInst %void %DbgExt DebugScope %main_info",
+        "%no_scope = OpExtInst %void %DbgExt DebugNoScope",
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionForwardReference) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionMissingOpFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbgNone = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %dbgNone
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugScopeBeforeOpVariableInFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
+%foo = OpVariable %f32_ptr_function Function
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeSizeDebugInfoNone) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%ty_name = OpString "struct VS_OUTPUT"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeForwardReference) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_color_name = OpString "color : COLOR"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %VS_OUTPUT_color_info
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+%VS_OUTPUT_color_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_color_name %v4float_info %dbg_src 3 3 %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeMissingReference) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+  float4 color : COLOR;
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_color_name = OpString "color : COLOR"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %VS_OUTPUT_color_info
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("forward referenced IDs have not been defined"));
+}
+
+TEST_P(ValidateXDebugInfo, DebugSourceWrongResultType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %bool %DbgExt DebugSource %src %code
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     GetParam(), "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected result type must be a result id of "
+                        "OpTypeVoid"));
+}
+
+TEST_P(ValidateXDebugInfo, DebugSourceFailFile) {
+  const std::string src = R"(
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %DbgExt %code
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     GetParam(), "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand File must be a result id of "
+                        "OpString"));
+}
+
+TEST_P(ValidateXDebugInfo, DebugSourceFailSource) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %DbgExt
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     GetParam(), "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Text must be a result id of "
+                        "OpString"));
+}
+
+TEST_P(ValidateXDebugInfo, DebugSourceNoText) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     GetParam(), "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+INSTANTIATE_TEST_SUITE_P(OpenCLAndVkDebugInfo100, ValidateXDebugInfo,
+                         ::testing::ValuesIn(std::vector<std::string>{
+                             R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)",
+                             R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)",
+                         }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugCompilationUnit) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugCompilationUnitFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %src HLSL
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Source must be a result id of "
+                        "DebugSource"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugCompilationUnitFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+)";
+
+  const std::string dbg_inst = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %src %u32_5
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, dbg_inst,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Source must be a result id of "
+                        "DebugSource"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeBasicFailName) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %int_32 %int_32 Float
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Name must be a result id of "
+                        "OpString"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeBasicFailName) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %u32_32 %u32_32 %u32_3 %u32_0
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Name must be a result id of "
+                        "OpString"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeBasicFailSize) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %float_name Float
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Size must be a result id of "
+                        "OpConstant"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeBasicFailSize) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %float_name %u32_3 %u32_0
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Size must be a result id of "
+                        "OpConstant"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypePointer) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%pfloat_info = OpExtInst %void %DbgExt DebugTypePointer %float_info Function FlagIsLocal
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypePointerFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%pfloat_info = OpExtInst %void %DbgExt DebugTypePointer %dbg_src Function FlagIsLocal
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeQualifier) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %float_info ConstType
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeQualifierFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %comp_unit ConstType
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeQualifier) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %float_info %u32_0
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeQualifierFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float4 main(float arg) {
+  float foo;
+  return float4(0, 0, 0, 0);
+}
+"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %comp_unit %u32_0
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArray) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %int_32
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayWithVariableSize) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%int_name = OpString "int"
+%main_name = OpString "main"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%uint_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %int_32 Unsigned
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %uint_info %dbg_src 1 1 %main_info FlagIsLocal
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailBaseType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %comp_unit %int_32
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type is not a valid debug "
+                        "type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCount) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %float_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountFloat) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %f32_4
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountZero) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %u32_0
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailVariableSizeTypeFloat) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%main_name = OpString "main"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 1 %main_info FlagIsLocal
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArray) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %u32_32
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayWithVariableSize) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%uint_name = OpString "uint"
+%main_name = OpString "main"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_6 = OpConstant %u32 6
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%uint_info = OpExtInst %void %DbgExt DebugTypeBasic %uint_name %u32_32 %u32_6 %u32_0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_1 %u32_1 %comp_unit %main_name %u32_3 %u32_1
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %uint_info %dbg_src %u32_1 %u32_1 %main_info %u32_4
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailBaseType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %comp_unit %u32_32
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type is not a valid debug "
+                        "type"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailComponentCount) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %float_info
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailComponentCountFloat) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %f32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailComponentCountZero) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %u32_0
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailVariableSizeTypeFloat) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%main_name = OpString "main"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_6 = OpConstant %u32 6
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_1 %u32_1 %comp_unit %main_name %u32_3 %u32_1
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src %u32_1 %u32_1 %main_info %u32_4
+%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be OpConstant with a 32- or "
+                        "64-bits integer scalar type or DebugGlobalVariable or "
+                        "DebugLocalVariable with a 32- or 64-bits unsigned "
+                        "integer scalar type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVector) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 4
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFailComponentZero) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 0
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFailComponentFive) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 5
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeVector) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeVectorFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src %u32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Base Type must be a result id of "
+                        "DebugTypeBasic"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeVectorFailComponentZero) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_0
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be positive "
+                        "integer less than or equal to 4"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeVectorFailComponentFive) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_5
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Component Count must be positive "
+                        "integer less than or equal to 4"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypedef) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugTypedef %foo_name %float_info %dbg_src 1 1 %comp_unit
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypedef, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugTypedef )";
+  ss << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second +
+                        " must be a result id of "));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypedef,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %float_info %dbg_src 1 1 %comp_unit)",
+                       "Name"),
+        std::make_pair(R"(%foo_name %dbg_src %dbg_src 1 1 %comp_unit)",
+                       "Base Type"),
+        std::make_pair(R"(%foo_name %float_info %comp_unit 1 1 %comp_unit)",
+                       "Source"),
+        std::make_pair(R"(%foo_name %float_info %dbg_src 1 1 %dbg_src)",
+                       "Parent"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypedef) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo_info = OpExtInst %void %DbgExt DebugTypedef %foo_name %float_info %dbg_src %u32_1 %u32_1 %comp_unit
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugTypedef, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo_info = OpExtInst %void %DbgExt DebugTypedef )";
+  ss << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second +
+                        " must be a result id of "));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugTypedef,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %float_info %dbg_src %u32_1 %u32_1 %comp_unit)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src %u32_1 %u32_1 %comp_unit)",
+            "Base Type"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit %u32_1 %u32_1 %comp_unit)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_1 %u32_1 %dbg_src)",
+            "Parent"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%main_type_info1 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_type_info2 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info
+%main_type_info3 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info %float_info
+%main_type_info4 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void %float_info %float_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunctionFailReturn) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %dbg_src %float_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected operand Return Type is not a valid debug type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunctionFailParam) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info %void
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected operand Parameter Types is not a valid debug type"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeFunctionAndParams) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%main_type_info1 = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_type_info2 = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %float_info
+%main_type_info3 = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %float_info %float_info
+%main_type_info4 = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void %float_info %float_info
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeFunctionFailReturn) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %dbg_src %float_info
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected operand Return Type is not a valid debug type"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeFunctionFailParam) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+%float_name = OpString "float"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %float_info %void
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("expected operand Parameter Types is not a valid debug type"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeEnum) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%none = OpExtInst %void %DbgExt DebugInfoNone
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info1 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name %u32_1 %foo_name
+%foo_info2 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name %u32_1 %foo_name
+%foo_info3 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeEnum, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugTypeEnum )";
+  ss << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeEnum,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Underlying Types"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %dbg_src %int_32 FlagIsPublic %u32_0 %foo_name)",
+            "Parent"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %void FlagIsPublic %u32_0 %foo_name)",
+            "Size"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %u32_0 FlagIsPublic %u32_0 %foo_name)",
+            "Size"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %foo_name %foo_name)",
+            "Value"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %u32_1)",
+            "Name"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeEnum) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%none = OpExtInst %void %DbgExt DebugInfoNone
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo_info1 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %float_info %dbg_src %u32_1 %u32_1 %comp_unit %u32_32 %u32_3 %u32_0 %foo_name %u32_1 %foo_name
+%foo_info2 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src %u32_1 %u32_1 %comp_unit %u32_32 %u32_3 %u32_0 %foo_name %u32_1 %foo_name
+%foo_info3 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src %u32_1 %u32_1 %comp_unit %u32_32 %u32_3
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugTypeEnum, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo_info = OpExtInst %void %DbgExt DebugTypeEnum )";
+  ss << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugTypeEnum,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %float_info %dbg_src %u32_1 %u32_1 %comp_unit %u32_32 %u32_3 %u32_0 %foo_name)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src %u32_1 %u32_1 %comp_unit %u32_32 %u32_3 %u32_0 %foo_name)",
+            "Underlying Types"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit %u32_1 %u32_1 %comp_unit %u32_32 %u32_3 %u32_0 %foo_name)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_1 %u32_1 %dbg_src %u32_32 %u32_3 %u32_0 %foo_name)",
+            "Parent"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_1 %u32_1 %comp_unit %void %u32_3 %u32_0 %foo_name)",
+            "Size"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_1 %u32_1 %comp_unit %u32_0 %u32_3 %u32_0 %foo_name)",
+            "Size"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_1 %u32_1 %comp_unit %u32_32 %u32_3 %foo_name %foo_name)",
+            "Value"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_1 %u32_1 %comp_unit %u32_32 %u32_3 %u32_0 %u32_1)",
+            "Name"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeFunctionAndInheritance) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+struct foo : VS_OUTPUT {
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
+%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%child = OpExtInst %void %DbgExt DebugTypeInheritance %foo_info %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeComposite, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+struct foo : VS_OUTPUT {
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite )";
+  ss << param.first;
+  ss << R"(
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
+%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%child = OpExtInst %void %DbgExt DebugTypeInheritance %foo_info %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second + " must be "));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeComposite,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %comp_unit 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Source"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %dbg_src %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Parent"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %int_128 %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %dbg_src FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
+            "Size"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %dbg_src %main_info %child)",
+            "Members"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %dbg_src %child)",
+            "Members"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %dbg_src)",
+            "Members"),
+    }));
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeMember, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float pos : SV_POSITION;
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_32 FlagIsPublic %VS_OUTPUT_pos_info
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember )";
+  ss << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  if (!param.second.empty()) {
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("expected operand " + param.second +
+                          " must be a result id of "));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeMember,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
+            "Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %dbg_src %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
+            ""),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %float_info 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
+            "Source"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %float_info %u32_0 %int_32 FlagIsPublic)",
+            "Parent"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %VS_OUTPUT_info %void %int_32 FlagIsPublic)",
+            "Offset"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %void FlagIsPublic)",
+            "Size"),
+    }));
+
+TEST_P(ValidateOpenCL100DebugInfoDebugTypeInheritance, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {};
+struct foo : VS_OUTPUT {};
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%foo_name = OpString "foo"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_name %u32_0 FlagIsPublic %child
+%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%bar_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Union %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
+%child = OpExtInst %void %DbgExt DebugTypeInheritance )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeInheritance,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %VS_OUTPUT_info %u32_0 %u32_0 FlagIsPublic)",
+                       "Child must be a result id of"),
+        std::make_pair(R"(%foo_info %dbg_src %u32_0 %u32_0 FlagIsPublic)",
+                       "Parent must be a result id of"),
+        std::make_pair(
+            R"(%bar_info %VS_OUTPUT_info %u32_0 %u32_0 FlagIsPublic)",
+            "Child must be class or struct debug type"),
+        std::make_pair(R"(%foo_info %bar_info %u32_0 %u32_0 FlagIsPublic)",
+                       "Parent must be class or struct debug type"),
+        std::make_pair(R"(%foo_info %VS_OUTPUT_info %void %u32_0 FlagIsPublic)",
+                       "Offset"),
+        std::make_pair(R"(%foo_info %VS_OUTPUT_info %u32_0 %void FlagIsPublic)",
+                       "Size"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeComposite) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+struct foo : VS_OUTPUT {
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%u32_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src %u32_2 %u32_3 %u32_0 %u32_128 %u32_3
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %VS_OUTPUT_linkage_name %u32_128 %u32_3 %VS_OUTPUT_pos_info
+%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %foo_name %u32_0 %u32_3
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugTypeComposite, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+struct foo : VS_OUTPUT {
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%u32_128 = OpConstant %u32 128
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src %u32_2 %u32_3 %u32_0 %u32_128 %u32_3
+%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite )";
+  ss << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second + " must be "));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugTypeComposite,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %VS_OUTPUT_linkage_name %u32_128 %u32_3 %VS_OUTPUT_pos_info)",
+            "Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name %u32_1 %comp_unit %u32_1 %u32_1 %comp_unit %VS_OUTPUT_linkage_name %u32_128 %u32_3 %VS_OUTPUT_pos_info)",
+            "Source"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name %u32_1 %dbg_src %u32_1 %u32_1 %dbg_src %VS_OUTPUT_linkage_name %u32_128 %u32_3 %VS_OUTPUT_pos_info)",
+            "Parent"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %u32_128 %u32_128 %u32_3 %VS_OUTPUT_pos_info)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %VS_OUTPUT_linkage_name %dbg_src %u32_3 %VS_OUTPUT_pos_info)",
+            "Size"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %VS_OUTPUT_linkage_name %u32_128 %dbg_src %VS_OUTPUT_pos_info)",
+            "Flags"),
+        std::make_pair(
+            R"(%VS_OUTPUT_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %VS_OUTPUT_linkage_name %u32_128 %u32_3 %dbg_src)",
+            "Members"),
+    }));
+
+TEST_P(ValidateVulkan100DebugInfoDebugTypeMember, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float pos : SV_POSITION;
+};
+main() {}
+"
+%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
+%float_name = OpString "float"
+%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
+%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+%u32_128 = OpConstant %u32 128
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember )";
+  ss << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  if (!param.second.empty()) {
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("expected operand " + param.second +
+                          " must be a result id of "));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugTypeMember,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%dbg_src %float_info %dbg_src %u32_2 %u32_3 %u32_0 %u32_32 %u32_3)",
+            "Name"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %dbg_src %dbg_src %u32_2 %u32_3 %u32_0 %u32_32 %u32_3)",
+            ""),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %float_info %u32_2 %u32_3 %u32_0 %u32_32 %u32_3)",
+            "Source"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src %u32_2 %u32_3 %void %u32_32 %u32_3)",
+            "Offset"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src %u32_2 %u32_3 %u32_0 %void %u32_3)",
+            "Size"),
+        std::make_pair(
+            R"(%VS_OUTPUT_pos_name %float_info %dbg_src %u32_2 %u32_3 %u32_0 %u32_32 %void)",
+            "Flags"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionDeclaration) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugFunction, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic
+%main_info = OpExtInst %void %DbgExt DebugFunction )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugFunction,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%u32_0 %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
+            "Name"),
+        std::make_pair(
+            R"(%main_name %dbg_src %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
+            "Type"),
+        std::make_pair(
+            R"(%main_name %main_type_info %comp_unit 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
+            "Source"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %dbg_src %main_linkage_name FlagIsPublic 13 %main)",
+            "Parent"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %void FlagIsPublic 13 %main)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %void)",
+            "Function"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main %dbg_src)",
+            "Declaration"),
+    }));
+
+TEST_P(ValidateOpenCL100DebugInfoDebugFunctionDeclaration, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail,
+    ValidateOpenCL100DebugInfoDebugFunctionDeclaration,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%u32_0 %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
+            "Name"),
+        std::make_pair(
+            R"(%main_name %dbg_src %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
+            "Type"),
+        std::make_pair(
+            R"(%main_name %main_type_info %comp_unit 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
+            "Source"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %dbg_src %main_linkage_name FlagIsPublic)",
+            "Parent"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %void FlagIsPublic)",
+            "Linkage Name"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugFunctionDeclaration) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_12 = OpConstant %u32 12
+%u32_13 = OpConstant %u32 13
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3 %u32_13
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugFunction, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_12 = OpConstant %u32 12
+%u32_13 = OpConstant %u32 13
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3
+%main_info = OpExtInst %void %DbgExt DebugFunction )"
+     << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugFunction,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%u32_0 %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3 %u32_13)",
+            "Name"),
+        std::make_pair(
+            R"(%main_name %dbg_src %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3 %u32_13)",
+            "Type"),
+        std::make_pair(
+            R"(%main_name %main_type_info %comp_unit %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3 %u32_13)",
+            "Source"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src %u32_12 %u32_1 %dbg_src %main_linkage_name %u32_3 %u32_13)",
+            "Parent"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %void %u32_3 %u32_13)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3 %u32_13 %dbg_src)",
+            "Declaration"),
+    }));
+
+TEST_P(ValidateVulkan100DebugInfoDebugFunctionDeclaration, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "struct VS_OUTPUT {
+  float4 pos : SV_POSITION;
+};
+main() {}
+"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v4f_main_f"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_12 = OpConstant %u32 12
+%u32_13 = OpConstant %u32 13
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration )"
+     << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail,
+    ValidateVulkan100DebugInfoDebugFunctionDeclaration,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%u32_0 %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3)",
+            "Name"),
+        std::make_pair(
+            R"(%main_name %dbg_src %dbg_src %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3)",
+            "Type"),
+        std::make_pair(
+            R"(%main_name %main_type_info %comp_unit %u32_12 %u32_1 %comp_unit %main_linkage_name %u32_3)",
+            "Source"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src %u32_12 %u32_1 %dbg_src %main_linkage_name %u32_3)",
+            "Parent"),
+        std::make_pair(
+            R"(%main_name %main_type_info %dbg_src %u32_12 %u32_1 %comp_unit %void %u32_3)",
+            "Linkage Name"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugLexicalBlock) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_block = OpExtInst %void %DbgExt DebugLexicalBlock %dbg_src 1 1 %comp_unit %main_name)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugLexicalBlock, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_block = OpExtInst %void %DbgExt DebugLexicalBlock )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
+                                                     extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugLexicalBlock,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%comp_unit 1 1 %comp_unit %main_name)", "Source"),
+        std::make_pair(R"(%dbg_src 1 1 %dbg_src %main_name)", "Parent"),
+        std::make_pair(R"(%dbg_src 1 1 %comp_unit %void)", "Name"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugScopeFailScope) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %dbg_src
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugScopeFailInlinedAt) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %comp_unit %dbg_src
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined At"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugLexicalBlock) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_block = OpExtInst %void %DbgExt DebugLexicalBlock %dbg_src %u32_1 %u32_1 %comp_unit %main_name
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugLexicalBlock, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "main() {}"
+%main_name = OpString "main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_block = OpExtInst %void %DbgExt DebugLexicalBlock )"
+     << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugLexicalBlock,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%comp_unit %u32_1 %u32_1 %comp_unit %main_name)",
+                       "Source"),
+        std::make_pair(R"(%dbg_src %u32_1 %u32_1 %dbg_src %main_name)",
+                       "Parent"),
+        std::make_pair(R"(%dbg_src %u32_1 %u32_1 %comp_unit %void)", "Name"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugScopeFailScope) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %dbg_src
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugScopeFailInlinedAt) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %comp_unit %dbg_src
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined At"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugLocalVariable) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugLocalVariable, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugLocalVariable )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugLocalVariable,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%void %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src 1 10 %comp_unit FlagIsLocal 0)",
+            "Type"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit 1 10 %comp_unit FlagIsLocal 0)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 1 10 %dbg_src FlagIsLocal 0)",
+            "Parent"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugLocalVariable) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_10 = OpConstant %u32 10
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src %u32_1 %u32_10 %comp_unit %u32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugLocalVariable, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_10 = OpConstant %u32 10
+%u32_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo = OpExtInst %void %DbgExt DebugLocalVariable )"
+     << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugLocalVariable,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%void %float_info %dbg_src %u32_1 %u32_10 %comp_unit %u32_3 %u32_0)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src %u32_1 %u32_10 %comp_unit %u32_3 %u32_0)",
+            "Type"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit %u32_1 %u32_10 %comp_unit %u32_3 %u32_0)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_1 %u32_10 %dbg_src %u32_3 %u32_0)",
+            "Parent"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugDeclare) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const std::string body = R"(
+%foo = OpVariable %f32_ptr_function Function
+%decl = OpExtInst %void %DbgExt DebugDeclare %foo_info %foo %null_expr
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugDeclareParam) {
+  CompileSuccessfully(R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_COLOR
+          %4 = OpString "test.hlsl"
+               OpSource HLSL 620 %4 "#line 1 \"test.hlsl\"
+void main(float foo:COLOR) {}
+"
+         %11 = OpString "#line 1 \"test.hlsl\"
+void main(float foo:COLOR) {}
+"
+         %14 = OpString "float"
+         %17 = OpString "src.main"
+         %20 = OpString "foo"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %main "main"
+               OpName %param_var_foo "param.var.foo"
+               OpName %src_main "src.main"
+               OpName %foo "foo"
+               OpName %bb_entry "bb.entry"
+               OpDecorate %in_var_COLOR Location 0
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+         %29 = OpTypeFunction %void %_ptr_Function_float
+               OpLine %4 1 21
+%in_var_COLOR = OpVariable %_ptr_Input_float Input
+         %10 = OpExtInst %void %1 DebugExpression
+         %12 = OpExtInst %void %1 DebugSource %4 %11
+         %13 = OpExtInst %void %1 DebugCompilationUnit 1 4 %12 HLSL
+         %15 = OpExtInst %void %1 DebugTypeBasic %14 %uint_32 Float
+         %16 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %void %15
+         %18 = OpExtInst %void %1 DebugFunction %17 %16 %12 1 1 %13 %17 FlagIsProtected|FlagIsPrivate 1 %src_main
+         %21 = OpExtInst %void %1 DebugLocalVariable %20 %15 %12 1 17 %18 FlagIsLocal 0
+         %22 = OpExtInst %void %1 DebugLexicalBlock %12 1 28 %18
+               OpLine %4 1 1
+       %main = OpFunction %void None %23
+         %24 = OpLabel
+               OpLine %4 1 17
+%param_var_foo = OpVariable %_ptr_Function_float Function
+         %27 = OpLoad %float %in_var_COLOR
+               OpLine %4 1 1
+         %28 = OpFunctionCall %void %src_main %param_var_foo
+               OpReturn
+               OpFunctionEnd
+   %src_main = OpFunction %void None %29
+               OpLine %4 1 17
+        %foo = OpFunctionParameter %_ptr_Function_float
+         %31 = OpExtInst %void %1 DebugDeclare %21 %foo %10
+   %bb_entry = OpLabel
+               OpLine %4 1 29
+               OpReturn
+               OpFunctionEnd
+)");
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugDeclare, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%foo = OpVariable %f32_ptr_function Function
+%decl = OpExtInst %void %DbgExt DebugDeclare )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, ss.str(), extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugDeclare,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %foo %null_expr)", "Local Variable"),
+        std::make_pair(R"(%foo_info %void %null_expr)", "Variable"),
+        std::make_pair(R"(%foo_info %foo %dbg_src)", "Expression"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugDeclare) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_10 = OpConstant %u32 10
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src %u32_1 %u32_10 %comp_unit %u32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string body = R"(
+%foo = OpVariable %f32_ptr_function Function
+%decl = OpExtInst %void %DbgExt DebugDeclare %foo_info %foo %null_expr
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugDeclareParam) {
+  CompileSuccessfully(R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_non_semantic_info"
+          %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_COLOR
+          %4 = OpString "test.hlsl"
+               OpSource HLSL 620 %4 "#line 1 \"test.hlsl\"
+void main(float foo:COLOR) {}
+"
+         %11 = OpString "#line 1 \"test.hlsl\"
+void main(float foo:COLOR) {}
+"
+         %14 = OpString "float"
+         %17 = OpString "src.main"
+         %20 = OpString "foo"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %main "main"
+               OpName %param_var_foo "param.var.foo"
+               OpName %src_main "src.main"
+               OpName %foo "foo"
+               OpName %bb_entry "bb.entry"
+               OpDecorate %in_var_COLOR Location 0
+       %uint = OpTypeInt 32 0
+      %u32_0 = OpConstant %uint 0
+      %u32_1 = OpConstant %uint 1
+      %u32_2 = OpConstant %uint 2
+      %u32_3 = OpConstant %uint 3
+      %u32_4 = OpConstant %uint 4
+      %u32_5 = OpConstant %uint 5
+     %u32_10 = OpConstant %uint 10
+     %u32_17 = OpConstant %uint 17
+     %u32_28 = OpConstant %uint 28
+     %u32_32 = OpConstant %uint 32
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%_ptr_Function_float = OpTypePointer Function %float
+         %29 = OpTypeFunction %void %_ptr_Function_float
+               OpLine %4 1 21
+%in_var_COLOR = OpVariable %_ptr_Input_float Input
+         %10 = OpExtInst %void %1 DebugExpression
+         %12 = OpExtInst %void %1 DebugSource %4 %11
+         %13 = OpExtInst %void %1 DebugCompilationUnit %u32_1 %u32_4 %12 %u32_5
+         %15 = OpExtInst %void %1 DebugTypeBasic %14 %uint_32 %u32_3 %u32_0
+         %16 = OpExtInst %void %1 DebugTypeFunction %u32_3 %void %15
+         %18 = OpExtInst %void %1 DebugFunction %17 %16 %12 %u32_1 %u32_1 %13 %17 %u32_3 %u32_1
+         %21 = OpExtInst %void %1 DebugLocalVariable %20 %15 %12 %u32_1 %u32_17 %18 %u32_4 %u32_0
+         %22 = OpExtInst %void %1 DebugLexicalBlock %12 %u32_1 %u32_28 %18
+       %main = OpFunction %void None %23
+         %24 = OpLabel
+%param_var_foo = OpVariable %_ptr_Function_float Function
+         %27 = OpLoad %float %in_var_COLOR
+         %28 = OpFunctionCall %void %src_main %param_var_foo
+               OpReturn
+               OpFunctionEnd
+   %src_main = OpFunction %void None %29
+        %foo = OpFunctionParameter %_ptr_Function_float
+         %31 = OpExtInst %void %1 DebugDeclare %21 %foo %10
+   %bb_entry = OpLabel
+               OpReturn
+               OpFunctionEnd
+)");
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugDeclare, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_10 = OpConstant %u32 10
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src %u32_1 %u32_10 %comp_unit %u32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%foo = OpVariable %f32_ptr_function Function
+%decl = OpExtInst %void %DbgExt DebugDeclare )"
+     << param.first;
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, ss.str(), extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllVulkan100DebugInfoFail, ValidateVulkan100DebugInfoDebugDeclare,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %foo %null_expr)", "Local Variable"),
+        std::make_pair(R"(%foo_info %void %null_expr)", "Variable"),
+        std::make_pair(R"(%foo_info %foo %dbg_src)", "Expression"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugExpression) {
+  const std::string dbg_inst_header = R"(
+%op0 = OpExtInst %void %DbgExt DebugOperation Deref
+%op1 = OpExtInst %void %DbgExt DebugOperation Plus
+%null_expr = OpExtInst %void %DbgExt DebugExpression %op0 %op1
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugExpressionFail) {
+  const std::string dbg_inst_header = R"(
+%op = OpExtInst %void %DbgExt DebugOperation Deref
+%null_expr = OpExtInst %void %DbgExt DebugExpression %op %void
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "expected operand Operation must be a result id of DebugOperation"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugExpression) {
+  const std::string dbg_inst_header = R"(
+%op0 = OpExtInst %void %DbgExt DebugOperation %u32_0
+%op1 = OpExtInst %void %DbgExt DebugOperation %u32_1
+%null_expr = OpExtInst %void %DbgExt DebugExpression %op0 %op1
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugExpressionFail) {
+  const std::string dbg_inst_header = R"(
+%op = OpExtInst %void %DbgExt DebugOperation %u32_0
+%null_expr = OpExtInst %void %DbgExt DebugExpression %op %void
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "expected operand Operation must be a result id of DebugOperation"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplate) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateUsedForVariableType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %temp %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%main_name = OpString "main"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %param %param
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %main_info %param
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFailTarget) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%main_name = OpString "main"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %float_info %param
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Target must be DebugTypeComposite or "
+                        "DebugFunction"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFailParam) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%main_name = OpString "main"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+%int_128 = OpConstant %u32 128
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %param %param
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %main_info %float_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "expected operand Parameters must be DebugTypeTemplateParameter or "
+          "DebugTypeTemplateTemplateParameter"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeTemplate) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %ty_name %dbg_none %u32_3
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src %u32_0 %u32_0
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeTemplateUsedForVariableType) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %ty_name %dbg_none %u32_3
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src %u32_0 %u32_0
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %temp %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %f32_input %u32_3
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeTemplateFunction) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%main_name = OpString "main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src %u32_0 %u32_0
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %param %param
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_1 %u32_1 %comp_unit %main_name %u32_3 %u32_1
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %main_info %param
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeTemplateFailTarget) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%main_name = OpString "main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src %u32_0 %u32_0
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %float_info %param
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand Target must be DebugTypeComposite or "
+                        "DebugFunction"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugTypeTemplateFailParam) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "OpaqueType foo;
+main() {}
+"
+%float_name = OpString "float"
+%ty_name = OpString "Texture"
+%t_name = OpString "T"
+%main_name = OpString "main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name %u32_1 %dbg_src %u32_1 %u32_1 %comp_unit %ty_name %dbg_none %u32_3
+%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src %u32_0 %u32_0
+%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %float_info
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "expected operand Parameters must be DebugTypeTemplateParameter or "
+          "DebugTypeTemplateTemplateParameter"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariable) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableStaticMember) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%t = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Class %dbg_src 0 0 %comp_unit %foo_name %int_32 FlagIsPublic %a
+%a = OpExtInst %void %DbgExt DebugTypeMember %foo_name %float_info %dbg_src 0 0 %t %u32_0 %int_32 FlagIsPublic
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate %a
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableDebugInfoNone) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbgNone = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %dbgNone FlagIsProtected|FlagIsPrivate
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableConst) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %int_32 FlagIsProtected|FlagIsPrivate
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugGlobalVariable, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugGlobalVariable,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%void %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
+            "Type"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 0 0 %dbg_src %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
+            "Scope"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 0 0 %comp_unit %void %f32_input FlagIsProtected|FlagIsPrivate)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %void FlagIsProtected|FlagIsPrivate)",
+            "Variable"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariable) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %f32_input %u32_3
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariableStaticMember) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%a = OpExtInst %void %DbgExt DebugTypeMember %foo_name %float_info %dbg_src %u32_0 %u32_0 %u32_0 %u32_32 %u32_3
+%t = OpExtInst %void %DbgExt DebugTypeComposite %foo_name %u32_1 %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %u32_32 %u32_3 %a
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %t %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %f32_input %u32_3
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariableDebugInfoNone) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbgNone = OpExtInst %void %DbgExt DebugInfoNone
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %dbgNone %u32_3
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariableConst) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %u32_32 %u32_3
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, "", extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugGlobalVariable, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "float foo; void main() {}"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%foo = OpExtInst %void %DbgExt DebugGlobalVariable )"
+     << param.first;
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(),
+                                                     "", extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateVulkan100DebugInfoDebugGlobalVariable,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(
+            R"(%void %float_info %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %f32_input %u32_3)",
+            "Name"),
+        std::make_pair(
+            R"(%foo_name %dbg_src %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %f32_input %u32_3)",
+            "Type"),
+        std::make_pair(
+            R"(%foo_name %float_info %comp_unit %u32_0 %u32_0 %comp_unit %foo_name %f32_input %u32_3)",
+            "Source"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_0 %u32_0 %dbg_src %foo_name %f32_input %u32_3)",
+            "Scope"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_0 %u32_0 %comp_unit %void %f32_input %u32_3)",
+            "Linkage Name"),
+        std::make_pair(
+            R"(%foo_name %float_info %dbg_src %u32_0 %u32_0 %comp_unit %foo_name %void %u32_3)",
+            "Variable"),
+    }));
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAt) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
+%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info
+%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info %inlined_at
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAtFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
+%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info
+%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %inlined_at
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAtFail2) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
+%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info
+%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info %main_info
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, "", dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugInlinedAt) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_1 %u32_1 %comp_unit %main_name %u32_3 %u32_1
+%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt %u32_0 %main_info
+%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt %u32_0 %main_info %inlined_at
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugInlinedAtFail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_1 %u32_1 %comp_unit %main_name %u32_3 %u32_1
+%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt %u32_0 %main_info
+%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt %u32_0 %inlined_at %inlined_at
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope"));
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugInlinedAtFail2) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() {}"
+%void_name = OpString "void"
+%main_name = OpString "main"
+%main_linkage_name = OpString "v_main"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction %u32_3 %void
+%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src %u32_1 %u32_1 %comp_unit %main_name %u32_3 %u32_1
+%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt %u32_0 %main_info
+%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt %u32_0 %main_info %main_info
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string body = R"(
+%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined"));
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugValue) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_3 = OpConstant %u32 3
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const std::string body = R"(
+%value = OpExtInst %void %DbgExt DebugValue %foo_info %int_32 %null_expr %int_3
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateOpenCL100DebugInfo, DebugValueWithVariableIndex) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%int_name = OpString "int"
+%foo_name = OpString "foo"
+%len_name = OpString "length"
+)";
+
+  const std::string size_const = R"(
+%int_3 = OpConstant %u32 3
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %int_32 Signed
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src 1 10 %comp_unit FlagIsLocal
+%len_info = OpExtInst %void %DbgExt DebugLocalVariable %len_name %int_info %dbg_src 0 0 %comp_unit FlagIsLocal
+)";
+
+  const std::string body = R"(
+%value = OpExtInst %void %DbgExt DebugValue %foo_info %int_32 %null_expr %len_info
+)";
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateOpenCL100DebugInfoDebugValue, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string size_const = R"(
+%int_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%decl = OpExtInst %void %DbgExt DebugValue )"
+     << param.first;
+
+  const std::string extension = R"(
+%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, size_const, dbg_inst_header, ss.str(), extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugValue,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %int_32 %null_expr)", "Local Variable"),
+        std::make_pair(R"(%foo_info %int_32 %dbg_src)", "Expression"),
+        std::make_pair(R"(%foo_info %int_32 %null_expr %dbg_src)", "Indexes"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, DebugValue) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_10 = OpConstant %u32 10
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src %u32_1 %u32_10 %comp_unit %u32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string body = R"(
+%value = OpExtInst %void %DbgExt DebugValue %foo_info %u32_32 %null_expr %u32_3
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateVulkan100DebugInfo, DebugValueWithVariableIndex) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%int_name = OpString "int"
+%foo_name = OpString "foo"
+%len_name = OpString "length"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_10 = OpConstant %u32 10
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %u32_32 %u32_4 %u32_0
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src %u32_1 %u32_10 %comp_unit %u32_4 %u32_0
+%len_info = OpExtInst %void %DbgExt DebugLocalVariable %len_name %int_info %dbg_src %u32_0 %u32_0 %comp_unit %u32_4
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const std::string body = R"(
+%value = OpExtInst %void %DbgExt DebugValue %foo_info %u32_32 %null_expr %len_info
+)";
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, body, extension, "Vertex"));
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ValidateVulkan100DebugInfoDebugValue, Fail) {
+  const std::string src = R"(
+%src = OpString "simple.hlsl"
+%code = OpString "void main() { float foo; }"
+%float_name = OpString "float"
+%foo_name = OpString "foo"
+)";
+
+  const std::string constants = R"(
+%u32_4 = OpConstant %u32 4
+%u32_5 = OpConstant %u32 5
+%u32_10 = OpConstant %u32 10
+%u32_32 = OpConstant %u32 32
+)";
+
+  const std::string dbg_inst_header = R"(
+%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
+%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit %u32_2 %u32_4 %dbg_src %u32_5
+%null_expr = OpExtInst %void %DbgExt DebugExpression
+%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %u32_32 %u32_3 %u32_0
+%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info %u32_4
+%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src %u32_1 %u32_10 %comp_unit %u32_4 %u32_0
+)";
+
+  const std::string extension = R"(
+OpExtension "SPV_KHR_non_semantic_info"
+%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+)";
+
+  const auto& param = GetParam();
+
+  std::ostringstream ss;
+  ss << R"(
+%decl = OpExtInst %void %DbgExt DebugValue )"
+     << param.first;
+
+  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
+      src, constants, dbg_inst_header, ss.str(), extension, "Vertex"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("expected operand " + param.second));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    AllOpenCL100DebugInfoFail, ValidateVulkan100DebugInfoDebugValue,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair(R"(%dbg_src %u32_32 %null_expr %u32_3)",
+                       "Local Variable"),
+        std::make_pair(R"(%foo_info %u32_32 %dbg_src %u32_3)", "Expression"),
+        std::make_pair(R"(%foo_info %u32_32 %null_expr %dbg_src)", "Indexes"),
+    }));
+
+TEST_F(ValidateVulkan100DebugInfo, VulkanDebugInfoSample) {
+  std::ostringstream ss;
+  ss << R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_non_semantic_info"
+          %id_1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %id_MainPs "MainPs" %id_in_var_TEXCOORD2 %id_out_var_SV_Target0
+               OpExecutionMode %id_MainPs OriginUpperLeft
+          %id_7 = OpString "foo.frag"
+         %id_27 = OpString "float"
+         %id_32 = OpString "vColor"
+         %id_36 = OpString "PS_OUTPUT"
+         %id_42 = OpString "vTextureCoords"
+         %id_46 = OpString "PS_INPUT"
+         %id_49 = OpString "MainPs"
+         %id_50 = OpString ""
+         %id_55 = OpString "ps_output"
+         %id_59 = OpString "i"
+         %id_63 = OpString "@type.sampler"
+         %id_64 = OpString "type.sampler"
+         %id_66 = OpString "g_sAniso"
+         %id_69 = OpString "@type.2d.image"
+         %id_70 = OpString "type.2d.image"
+         %id_72 = OpString "TemplateParam"
+         %id_75 = OpString "g_tColor"
+               OpName %id_type_2d_image "type.2d.image"
+               OpName %id_g_tColor "g_tColor"
+               OpName %id_type_sampler "type.sampler"
+               OpName %id_g_sAniso "g_sAniso"
+               OpName %id_in_var_TEXCOORD2 "in.var.TEXCOORD2"
+               OpName %id_out_var_SV_Target0 "out.var.SV_Target0"
+               OpName %id_MainPs "MainPs"
+               OpName %id_PS_INPUT "PS_INPUT"
+               OpMemberName %id_PS_INPUT 0 "vTextureCoords"
+               OpName %id_param_var_i "param.var.i"
+               OpName %id_PS_OUTPUT "PS_OUTPUT"
+               OpMemberName %id_PS_OUTPUT 0 "vColor"
+               OpName %id_src_MainPs "src.MainPs"
+               OpName %id_i "i"
+               OpName %id_bb_entry "bb.entry"
+               OpName %id_ps_output "ps_output"
+               OpName %id_type_sampled_image "type.sampled.image"
+               OpDecorate %id_in_var_TEXCOORD2 Location 0
+               OpDecorate %id_out_var_SV_Target0 Location 0
+               OpDecorate %id_g_tColor DescriptorSet 0
+               OpDecorate %id_g_tColor Binding 0
+               OpDecorate %id_g_sAniso DescriptorSet 0
+               OpDecorate %id_g_sAniso Binding 1
+        %id_int = OpTypeInt 32 1
+      %id_int_0 = OpConstant %id_int 0
+       %id_uint = OpTypeInt 32 0
+    %id_uint_32 = OpConstant %id_uint 32
+      %id_float = OpTypeFloat 32
+%id_type_2d_image = OpTypeImage %id_float 2D 2 0 0 1 Unknown
+%id__ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %id_type_2d_image
+%id_type_sampler = OpTypeSampler
+%id__ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %id_type_sampler
+    %id_v2float = OpTypeVector %id_float 2
+%id__ptr_Input_v2float = OpTypePointer Input %id_v2float
+    %id_v4float = OpTypeVector %id_float 4
+%id__ptr_Output_v4float = OpTypePointer Output %id_v4float
+       %id_void = OpTypeVoid
+     %id_uint_1 = OpConstant %id_uint 1
+     %id_uint_4 = OpConstant %id_uint 4
+     %id_uint_5 = OpConstant %id_uint 5
+     %id_uint_3 = OpConstant %id_uint 3
+     %id_uint_0 = OpConstant %id_uint 0
+   %id_uint_128 = OpConstant %id_uint 128
+    %id_uint_12 = OpConstant %id_uint 12
+    %id_uint_10 = OpConstant %id_uint 10
+     %id_uint_8 = OpConstant %id_uint 8
+     %id_uint_2 = OpConstant %id_uint 2
+    %id_uint_64 = OpConstant %id_uint 64
+     %id_uint_7 = OpConstant %id_uint 7
+    %id_uint_15 = OpConstant %id_uint 15
+    %id_uint_16 = OpConstant %id_uint 16
+    %id_uint_17 = OpConstant %id_uint 17
+    %id_uint_29 = OpConstant %id_uint 29
+    %id_uint_14 = OpConstant %id_uint 14
+    %id_uint_11 = OpConstant %id_uint 11
+         %id_78 = OpTypeFunction %id_void
+   %id_PS_INPUT = OpTypeStruct %id_v2float
+%id__ptr_Function_PS_INPUT = OpTypePointer Function %id_PS_INPUT
+  %id_PS_OUTPUT = OpTypeStruct %id_v4float
+         %id_89 = OpTypeFunction %id_PS_OUTPUT %id__ptr_Function_PS_INPUT
+%id__ptr_Function_PS_OUTPUT = OpTypePointer Function %id_PS_OUTPUT
+    %id_uint_20 = OpConstant %id_uint 20
+    %id_uint_19 = OpConstant %id_uint 19
+    %id_uint_26 = OpConstant %id_uint 26
+    %id_uint_46 = OpConstant %id_uint 46
+%id__ptr_Function_v2float = OpTypePointer Function %id_v2float
+    %id_uint_57 = OpConstant %id_uint 57
+    %id_uint_78 = OpConstant %id_uint 78
+%id_type_sampled_image = OpTypeSampledImage %id_type_2d_image
+    %id_uint_81 = OpConstant %id_uint 81
+%id__ptr_Function_v4float = OpTypePointer Function %id_v4float
+   %id_g_tColor = OpVariable %id__ptr_UniformConstant_type_2d_image UniformConstant
+   %id_g_sAniso = OpVariable %id__ptr_UniformConstant_type_sampler UniformConstant
+%id_in_var_TEXCOORD2 = OpVariable %id__ptr_Input_v2float Input
+%id_out_var_SV_Target0 = OpVariable %id__ptr_Output_v4float Output
+         %id_22 = OpExtInst %id_void %id_1 DebugSource %id_7
+         %id_23 = OpExtInst %id_void %id_1 DebugCompilationUnit %id_uint_1 %id_uint_4 %id_22 %id_uint_5
+         %id_28 = OpExtInst %id_void %id_1 DebugTypeBasic %id_27 %id_uint_32 %id_uint_3 %id_uint_0
+         %id_31 = OpExtInst %id_void %id_1 DebugTypeVector %id_28 %id_uint_4
+         %id_34 = OpExtInst %id_void %id_1 DebugTypeMember %id_32 %id_31 %id_22 %id_uint_12 %id_uint_12 %id_uint_0 %id_uint_128 %id_uint_3
+         %id_37 = OpExtInst %id_void %id_1 DebugTypeComposite %id_36 %id_uint_1 %id_22 %id_uint_10 %id_uint_8 %id_23 %id_36 %id_uint_128 %id_uint_3 %id_34
+         %id_40 = OpExtInst %id_void %id_1 DebugTypeVector %id_28 %id_uint_2
+         %id_44 = OpExtInst %id_void %id_1 DebugTypeMember %id_42 %id_40 %id_22 %id_uint_7 %id_uint_12 %id_uint_0 %id_uint_64 %id_uint_3
+         %id_47 = OpExtInst %id_void %id_1 DebugTypeComposite %id_46 %id_uint_1 %id_22 %id_uint_5 %id_uint_8 %id_23 %id_46 %id_uint_64 %id_uint_3 %id_44
+         %id_48 = OpExtInst %id_void %id_1 DebugTypeFunction %id_uint_3 %id_37 %id_47
+         %id_51 = OpExtInst %id_void %id_1 DebugFunction %id_49 %id_48 %id_22 %id_uint_15 %id_uint_1 %id_23 %id_50 %id_uint_3 %id_uint_16
+         %id_54 = OpExtInst %id_void %id_1 DebugLexicalBlock %id_22 %id_uint_16 %id_uint_1 %id_51
+         %id_56 = OpExtInst %id_void %id_1 DebugLocalVariable %id_55 %id_37 %id_22 %id_uint_17 %id_uint_15 %id_54 %id_uint_4
+         %id_58 = OpExtInst %id_void %id_1 DebugExpression
+         %id_60 = OpExtInst %id_void %id_1 DebugLocalVariable %id_59 %id_47 %id_22 %id_uint_15 %id_uint_29 %id_51 %id_uint_4 %id_uint_1
+         %id_62 = OpExtInst %id_void %id_1 DebugInfoNone
+         %id_65 = OpExtInst %id_void %id_1 DebugTypeComposite %id_63 %id_uint_1 %id_22 %id_uint_0 %id_uint_0 %id_23 %id_64 %id_62 %id_uint_3
+         %id_67 = OpExtInst %id_void %id_1 DebugGlobalVariable %id_66 %id_65 %id_22 %id_uint_3 %id_uint_14 %id_23 %id_66 %id_g_sAniso %id_uint_8
+         %id_71 = OpExtInst %id_void %id_1 DebugTypeComposite %id_69 %id_uint_0 %id_22 %id_uint_0 %id_uint_0 %id_23 %id_70 %id_62 %id_uint_3
+         %id_73 = OpExtInst %id_void %id_1 DebugTypeTemplateParameter %id_72 %id_31 %id_62 %id_22 %id_uint_0 %id_uint_0
+         %id_74 = OpExtInst %id_void %id_1 DebugTypeTemplate %id_71 %id_73
+         %id_76 = OpExtInst %id_void %id_1 DebugGlobalVariable %id_75 %id_74 %id_22 %id_uint_1 %id_uint_11 %id_23 %id_75 %id_g_tColor %id_uint_8
+     %id_MainPs = OpFunction %id_void None %id_78
+         %id_79 = OpLabel
+%id_param_var_i = OpVariable %id__ptr_Function_PS_INPUT Function
+         %id_83 = OpLoad %id_v2float %id_in_var_TEXCOORD2
+         %id_84 = OpCompositeConstruct %id_PS_INPUT %id_83
+               OpStore %id_param_var_i %id_84
+         %id_86 = OpFunctionCall %id_PS_OUTPUT %id_src_MainPs %id_param_var_i
+         %id_88 = OpCompositeExtract %id_v4float %id_86 0
+               OpStore %id_out_var_SV_Target0 %id_88
+               OpReturn
+               OpFunctionEnd
+ %id_src_MainPs = OpFunction %id_PS_OUTPUT None %id_89
+          %id_i = OpFunctionParameter %id__ptr_Function_PS_INPUT
+   %id_bb_entry = OpLabel
+  %id_ps_output = OpVariable %id__ptr_Function_PS_OUTPUT Function
+         %id_94 = OpExtInst %id_void %id_1 DebugScope %id_51
+         %id_97 = OpExtInst %id_void %id_1 DebugDeclare %id_60 %id_i %id_58
+         %id_99 = OpExtInst %id_void %id_1 DebugFunctionDefinition %id_51 %id_src_MainPs
+        %id_100 = OpExtInst %id_void %id_1 DebugScope %id_54
+        %id_102 = OpExtInst %id_void %id_1 DebugDeclare %id_56 %id_ps_output %id_58
+        %id_106 = OpLoad %id_type_2d_image %id_g_tColor
+        %id_109 = OpLoad %id_type_sampler %id_g_sAniso
+        %id_114 = OpAccessChain %id__ptr_Function_v2float %id_i %id_int_0
+        %id_115 = OpLoad %id_v2float %id_114
+        %id_119 = OpSampledImage %id_type_sampled_image %id_106 %id_109
+        %id_120 = OpImageSampleImplicitLod %id_v4float %id_119 %id_115 None
+        %id_123 = OpAccessChain %id__ptr_Function_v4float %id_ps_output %id_int_0
+               OpStore %id_123 %id_120
+        %id_125 = OpLoad %id_PS_OUTPUT %id_ps_output
+               OpReturnValue %id_125
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(ss.str());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index 683a76f..2b6df04 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -33,33 +33,6 @@
 using ::testing::Not;
 
 using ValidateExtInst = spvtest::ValidateBase<bool>;
-using ValidateOldDebugInfo = spvtest::ValidateBase<std::string>;
-using ValidateOpenCL100DebugInfo = spvtest::ValidateBase<std::string>;
-using ValidateLocalDebugInfoOutOfFunction = spvtest::ValidateBase<std::string>;
-using ValidateOpenCL100DebugInfoDebugTypedef =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugTypeEnum =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugTypeComposite =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugTypeMember =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugTypeInheritance =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugFunction =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugFunctionDeclaration =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugLexicalBlock =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugLocalVariable =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugGlobalVariable =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugDeclare =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
-using ValidateOpenCL100DebugInfoDebugValue =
-    spvtest::ValidateBase<std::pair<std::string, std::string>>;
 using ValidateGlslStd450SqrtLike = spvtest::ValidateBase<std::string>;
 using ValidateGlslStd450FMinLike = spvtest::ValidateBase<std::string>;
 using ValidateGlslStd450FClampLike = spvtest::ValidateBase<std::string>;
@@ -488,1685 +461,6 @@
   return ss.str();
 }
 
-std::string GenerateShaderCodeForDebugInfo(
-    const std::string& op_string_instructions,
-    const std::string& op_const_instructions,
-    const std::string& debug_instructions_before_main, const std::string& body,
-    const std::string& capabilities_and_extensions = "",
-    const std::string& execution_model = "Fragment") {
-  std::ostringstream ss;
-  ss << R"(
-OpCapability Shader
-OpCapability Float16
-OpCapability Float64
-OpCapability Int16
-OpCapability Int64
-)";
-
-  ss << capabilities_and_extensions;
-  ss << "%extinst = OpExtInstImport \"GLSL.std.450\"\n";
-  ss << "OpMemoryModel Logical GLSL450\n";
-  ss << "OpEntryPoint " << execution_model << " %main \"main\""
-     << " %f32_output"
-     << " %f32vec2_output"
-     << " %u32_output"
-     << " %u32vec2_output"
-     << " %u64_output"
-     << " %f32_input"
-     << " %f32vec2_input"
-     << " %u32_input"
-     << " %u32vec2_input"
-     << " %u64_input"
-     << "\n";
-  if (execution_model == "Fragment") {
-    ss << "OpExecutionMode %main OriginUpperLeft\n";
-  }
-
-  ss << op_string_instructions;
-
-  ss << R"(
-%void = OpTypeVoid
-%func = OpTypeFunction %void
-%bool = OpTypeBool
-%f16 = OpTypeFloat 16
-%f32 = OpTypeFloat 32
-%f64 = OpTypeFloat 64
-%u32 = OpTypeInt 32 0
-%s32 = OpTypeInt 32 1
-%u64 = OpTypeInt 64 0
-%s64 = OpTypeInt 64 1
-%u16 = OpTypeInt 16 0
-%s16 = OpTypeInt 16 1
-%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
-%s32vec2 = OpTypeVector %s32 2
-%u32vec4 = OpTypeVector %u32 4
-%s32vec4 = OpTypeVector %s32 4
-%u64vec2 = OpTypeVector %u64 2
-%s64vec2 = OpTypeVector %s64 2
-%f64mat22 = OpTypeMatrix %f64vec2 2
-%f32mat22 = OpTypeMatrix %f32vec2 2
-%f32mat23 = OpTypeMatrix %f32vec2 3
-%f32mat32 = OpTypeMatrix %f32vec3 2
-%f32mat33 = OpTypeMatrix %f32vec3 3
-
-%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
-
-%f16_0 = OpConstant %f16 0
-%f16_1 = OpConstant %f16 1
-%f16_h = OpConstant %f16 0.5
-
-%u32_0 = OpConstant %u32 0
-%u32_1 = OpConstant %u32 1
-%u32_2 = OpConstant %u32 2
-%u32_3 = OpConstant %u32 3
-
-%s32_0 = OpConstant %s32 0
-%s32_1 = OpConstant %s32 1
-%s32_2 = OpConstant %s32 2
-%s32_3 = OpConstant %s32 3
-
-%u64_0 = OpConstant %u64 0
-%u64_1 = OpConstant %u64 1
-%u64_2 = OpConstant %u64 2
-%u64_3 = OpConstant %u64 3
-
-%s64_0 = OpConstant %s64 0
-%s64_1 = OpConstant %s64 1
-%s64_2 = OpConstant %s64 2
-%s64_3 = OpConstant %s64 3
-)";
-
-  ss << op_const_instructions;
-
-  ss << R"(
-%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1
-%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1
-
-%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2
-%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2
-
-%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3
-%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3
-
-%s64vec2_01 = OpConstantComposite %s64vec2 %s64_0 %s64_1
-%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1
-
-%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12
-%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12
-
-%f32_ptr_output = OpTypePointer Output %f32
-%f32vec2_ptr_output = OpTypePointer Output %f32vec2
-
-%u32_ptr_output = OpTypePointer Output %u32
-%u32vec2_ptr_output = OpTypePointer Output %u32vec2
-
-%u64_ptr_output = OpTypePointer Output %u64
-
-%f32_output = OpVariable %f32_ptr_output Output
-%f32vec2_output = OpVariable %f32vec2_ptr_output Output
-
-%u32_output = OpVariable %u32_ptr_output Output
-%u32vec2_output = OpVariable %u32vec2_ptr_output Output
-
-%u64_output = OpVariable %u64_ptr_output Output
-
-%f32_ptr_input = OpTypePointer Input %f32
-%f32vec2_ptr_input = OpTypePointer Input %f32vec2
-
-%u32_ptr_input = OpTypePointer Input %u32
-%u32vec2_ptr_input = OpTypePointer Input %u32vec2
-
-%u64_ptr_input = OpTypePointer Input %u64
-
-%f32_ptr_function = OpTypePointer Function %f32
-
-%f32_input = OpVariable %f32_ptr_input Input
-%f32vec2_input = OpVariable %f32vec2_ptr_input Input
-
-%u32_input = OpVariable %u32_ptr_input Input
-%u32vec2_input = OpVariable %u32vec2_ptr_input Input
-
-%u64_input = OpVariable %u64_ptr_input Input
-
-%u32_ptr_function = OpTypePointer Function %u32
-
-%struct_f16_u16 = OpTypeStruct %f16 %u16
-%struct_f32_f32 = OpTypeStruct %f32 %f32
-%struct_f32_f32_f32 = OpTypeStruct %f32 %f32 %f32
-%struct_f32_u32 = OpTypeStruct %f32 %u32
-%struct_f32_u32_f32 = OpTypeStruct %f32 %u32 %f32
-%struct_u32_f32 = OpTypeStruct %u32 %f32
-%struct_u32_u32 = OpTypeStruct %u32 %u32
-%struct_f32_f64 = OpTypeStruct %f32 %f64
-%struct_f32vec2_f32vec2 = OpTypeStruct %f32vec2 %f32vec2
-%struct_f32vec2_u32vec2 = OpTypeStruct %f32vec2 %u32vec2
-)";
-
-  ss << debug_instructions_before_main;
-
-  ss << R"(
-%main = OpFunction %void None %func
-%main_entry = OpLabel
-)";
-
-  ss << body;
-
-  ss << R"(
-OpReturn
-OpFunctionEnd)";
-
-  return ss.str();
-}
-
-TEST_F(ValidateOldDebugInfo, UseDebugInstructionOutOfFunction) {
-  const std::string src = R"(
-%code = OpString "main() {}"
-)";
-
-  const std::string dbg_inst = R"(
-%cu = OpExtInst %void %DbgExt DebugCompilationUnit %code 1 1
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "DebugInfo"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, UseDebugInstructionOutOfFunction) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugSourceInFunction) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", "", dbg_inst,
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Debug info extension instructions other than DebugScope, "
-                "DebugNoScope, DebugDeclare, DebugValue must appear between "
-                "section 9 (types, constants, global variables) and section 10 "
-                "(function declarations)"));
-}
-
-TEST_P(ValidateLocalDebugInfoOutOfFunction, OpenCLDebugInfo100DebugScope) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-%void_name = OpString "void"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-%int_name = OpString "int"
-%foo_name = OpString "foo"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %u32_0 Signed
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %int_info %dbg_src 1 1 %main_info FlagIsLocal
-%expr = OpExtInst %void %DbgExt DebugExpression
-)";
-
-  const std::string body = R"(
-%foo = OpVariable %u32_ptr_function Function
-%foo_val = OpLoad %u32 %foo
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header + GetParam(), body, extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("DebugScope, DebugNoScope, DebugDeclare, DebugValue "
-                        "of debug info extension must appear in a function "
-                        "body"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllLocalDebugInfo, ValidateLocalDebugInfoOutOfFunction,
-    ::testing::ValuesIn(std::vector<std::string>{
-        "%main_scope = OpExtInst %void %DbgExt DebugScope %main_info",
-        "%no_scope = OpExtInst %void %DbgExt DebugNoScope",
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionForwardReference) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-%void_name = OpString "void"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionMissingOpFunction) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-%void_name = OpString "void"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbgNone = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %dbgNone
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugScopeBeforeOpVariableInFunction) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float4 main(float arg) {
-  float foo;
-  return float4(0, 0, 0, 0);
-}
-"
-%float_name = OpString "float"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v4f_main_f"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %main_info
-%foo = OpVariable %f32_ptr_function Function
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeSizeDebugInfoNone) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "OpaqueType foo;
-main() {}
-"
-%ty_name = OpString "struct VS_OUTPUT"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeForwardReference) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float4 pos : SV_POSITION;
-  float4 color : COLOR;
-};
-main() {}
-"
-%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
-%float_name = OpString "float"
-%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
-%VS_OUTPUT_color_name = OpString "color : COLOR"
-%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %VS_OUTPUT_color_info
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
-%VS_OUTPUT_color_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_color_name %v4float_info %dbg_src 3 3 %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeMissingReference) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float4 pos : SV_POSITION;
-  float4 color : COLOR;
-};
-main() {}
-"
-%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
-%float_name = OpString "float"
-%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
-%VS_OUTPUT_color_name = OpString "color : COLOR"
-%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %VS_OUTPUT_color_info
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("forward referenced IDs have not been defined"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugInstructionWrongResultType) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %bool %DbgExt DebugSource %src %code
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected result type must be a result id of "
-                        "OpTypeVoid"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugCompilationUnit) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugCompilationUnitFail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %src HLSL
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Source must be a result id of "
-                        "DebugSource"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugSourceFailFile) {
-  const std::string src = R"(
-%code = OpString "main() {}"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %DbgExt %code
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand File must be a result id of "
-                        "OpString"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugSourceFailSource) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %DbgExt
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Text must be a result id of "
-                        "OpString"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugSourceNoText) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-)";
-
-  const std::string dbg_inst = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst, "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeBasicFailName) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float4 main(float arg) {
-  float foo;
-  return float4(0, 0, 0, 0);
-}
-"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %int_32 %int_32 Float
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Name must be a result id of "
-                        "OpString"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeBasicFailSize) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float4 main(float arg) {
-  float foo;
-  return float4(0, 0, 0, 0);
-}
-"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %float_name Float
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Size must be a result id of "
-                        "OpConstant"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypePointer) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float4 main(float arg) {
-  float foo;
-  return float4(0, 0, 0, 0);
-}
-"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%pfloat_info = OpExtInst %void %DbgExt DebugTypePointer %float_info Function FlagIsLocal
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypePointerFail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float4 main(float arg) {
-  float foo;
-  return float4(0, 0, 0, 0);
-}
-"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%pfloat_info = OpExtInst %void %DbgExt DebugTypePointer %dbg_src Function FlagIsLocal
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Base Type must be a result id of "
-                        "DebugTypeBasic"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeQualifier) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float4 main(float arg) {
-  float foo;
-  return float4(0, 0, 0, 0);
-}
-"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %float_info ConstType
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeQualifierFail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float4 main(float arg) {
-  float foo;
-  return float4(0, 0, 0, 0);
-}
-"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%cfloat_info = OpExtInst %void %DbgExt DebugTypeQualifier %comp_unit ConstType
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Base Type must be a result id of "
-                        "DebugTypeBasic"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArray) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %int_32
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayWithVariableSize) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-%int_name = OpString "int"
-%main_name = OpString "main"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%uint_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %int_32 Unsigned
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %uint_info %dbg_src 1 1 %main_info FlagIsLocal
-%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailBaseType) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %comp_unit %int_32
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Base Type is not a valid debug "
-                        "type"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCount) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %float_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Component Count must be OpConstant with a 32- or "
-                        "64-bits integer scalar type or DebugGlobalVariable or "
-                        "DebugLocalVariable with a 32- or 64-bits unsigned "
-                        "integer scalar type"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountFloat) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %f32_4
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Component Count must be OpConstant with a 32- or "
-                        "64-bits integer scalar type or DebugGlobalVariable or "
-                        "DebugLocalVariable with a 32- or 64-bits unsigned "
-                        "integer scalar type"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailComponentCountZero) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %u32_0
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Component Count must be OpConstant with a 32- or "
-                        "64-bits integer scalar type or DebugGlobalVariable or "
-                        "DebugLocalVariable with a 32- or 64-bits unsigned "
-                        "integer scalar type"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeArrayFailVariableSizeTypeFloat) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-%main_name = OpString "main"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 1 %main_info FlagIsLocal
-%float_arr_info = OpExtInst %void %DbgExt DebugTypeArray %float_info %foo_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Component Count must be OpConstant with a 32- or "
-                        "64-bits integer scalar type or DebugGlobalVariable or "
-                        "DebugLocalVariable with a 32- or 64-bits unsigned "
-                        "integer scalar type"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVector) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 4
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Base Type must be a result id of "
-                        "DebugTypeBasic"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFailComponentZero) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 0
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Base Type must be a result id of "
-                        "DebugTypeBasic"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeVectorFailComponentFive) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%vfloat_info = OpExtInst %void %DbgExt DebugTypeVector %dbg_src 5
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Base Type must be a result id of "
-                        "DebugTypeBasic"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypedef) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo_info = OpExtInst %void %DbgExt DebugTypedef %foo_name %float_info %dbg_src 1 1 %comp_unit
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugTypedef, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo_info = OpExtInst %void %DbgExt DebugTypedef )";
-  ss << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second +
-                        " must be a result id of "));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypedef,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(R"(%dbg_src %float_info %dbg_src 1 1 %comp_unit)",
-                       "Name"),
-        std::make_pair(R"(%foo_name %dbg_src %dbg_src 1 1 %comp_unit)",
-                       "Base Type"),
-        std::make_pair(R"(%foo_name %float_info %comp_unit 1 1 %comp_unit)",
-                       "Source"),
-        std::make_pair(R"(%foo_name %float_info %dbg_src 1 1 %dbg_src)",
-                       "Parent"),
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunction) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%main_type_info1 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_type_info2 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info
-%main_type_info3 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info %float_info
-%main_type_info4 = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void %float_info %float_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunctionFailReturn) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %dbg_src %float_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("expected operand Return Type is not a valid debug type"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeFunctionFailParam) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-%float_name = OpString "float"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %float_info %void
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("expected operand Parameter Types is not a valid debug type"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeEnum) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%none = OpExtInst %void %DbgExt DebugInfoNone
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo_info1 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name %u32_1 %foo_name
-%foo_info2 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name %u32_1 %foo_name
-%foo_info3 = OpExtInst %void %DbgExt DebugTypeEnum %foo_name %none %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugTypeEnum, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo_info = OpExtInst %void %DbgExt DebugTypeEnum )";
-  ss << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeEnum,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(
-            R"(%dbg_src %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
-            "Name"),
-        std::make_pair(
-            R"(%foo_name %dbg_src %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
-            "Underlying Types"),
-        std::make_pair(
-            R"(%foo_name %float_info %comp_unit 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %foo_name)",
-            "Source"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 1 1 %dbg_src %int_32 FlagIsPublic %u32_0 %foo_name)",
-            "Parent"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %void FlagIsPublic %u32_0 %foo_name)",
-            "Size"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %u32_0 FlagIsPublic %u32_0 %foo_name)",
-            "Size"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %foo_name %foo_name)",
-            "Value"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 1 1 %comp_unit %int_32 FlagIsPublic %u32_0 %u32_1)",
-            "Name"),
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeCompositeFunctionAndInheritance) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float4 pos : SV_POSITION;
-};
-struct foo : VS_OUTPUT {
-};
-main() {}
-"
-%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
-%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v4f_main_f"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
-%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
-%child = OpExtInst %void %DbgExt DebugTypeInheritance %foo_info %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugTypeComposite, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float4 pos : SV_POSITION;
-};
-struct foo : VS_OUTPUT {
-};
-main() {}
-"
-%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
-%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v4f_main_f"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite )";
-  ss << param.first;
-  ss << R"(
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember %VS_OUTPUT_pos_name %v4float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_128 FlagIsPublic
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %v4float_info %float_info
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main
-%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
-%child = OpExtInst %void %DbgExt DebugTypeInheritance %foo_info %VS_OUTPUT_info %int_128 %int_128 FlagIsPublic
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second + " must be "));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeComposite,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(
-            R"(%dbg_src Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
-            "Name"),
-        std::make_pair(
-            R"(%VS_OUTPUT_name Structure %comp_unit 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
-            "Source"),
-        std::make_pair(
-            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %dbg_src %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
-            "Parent"),
-        std::make_pair(
-            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %int_128 %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
-            "Linkage Name"),
-        std::make_pair(
-            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %dbg_src FlagIsPublic %VS_OUTPUT_pos_info %main_info %child)",
-            "Size"),
-        std::make_pair(
-            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %dbg_src %main_info %child)",
-            "Members"),
-        std::make_pair(
-            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %dbg_src %child)",
-            "Members"),
-        std::make_pair(
-            R"(%VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_128 FlagIsPublic %VS_OUTPUT_pos_info %main_info %dbg_src)",
-            "Members"),
-    }));
-
-TEST_P(ValidateOpenCL100DebugInfoDebugTypeMember, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float pos : SV_POSITION;
-};
-main() {}
-"
-%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
-%float_name = OpString "float"
-%VS_OUTPUT_pos_name = OpString "pos : SV_POSITION"
-%VS_OUTPUT_linkage_name = OpString "VS_OUTPUT"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_linkage_name %int_32 FlagIsPublic %VS_OUTPUT_pos_info
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%VS_OUTPUT_pos_info = OpExtInst %void %DbgExt DebugTypeMember )";
-  ss << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  if (!param.second.empty()) {
-    EXPECT_THAT(getDiagnosticString(),
-                HasSubstr("expected operand " + param.second +
-                          " must be a result id of "));
-  }
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeMember,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(
-            R"(%dbg_src %float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
-            "Name"),
-        std::make_pair(
-            R"(%VS_OUTPUT_pos_name %dbg_src %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
-            ""),
-        std::make_pair(
-            R"(%VS_OUTPUT_pos_name %float_info %float_info 2 3 %VS_OUTPUT_info %u32_0 %int_32 FlagIsPublic)",
-            "Source"),
-        std::make_pair(
-            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %float_info %u32_0 %int_32 FlagIsPublic)",
-            "Parent"),
-        std::make_pair(
-            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %VS_OUTPUT_info %void %int_32 FlagIsPublic)",
-            "Offset"),
-        std::make_pair(
-            R"(%VS_OUTPUT_pos_name %float_info %dbg_src 2 3 %VS_OUTPUT_info %u32_0 %void FlagIsPublic)",
-            "Size"),
-    }));
-
-TEST_P(ValidateOpenCL100DebugInfoDebugTypeInheritance, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {};
-struct foo : VS_OUTPUT {};
-"
-%VS_OUTPUT_name = OpString "struct VS_OUTPUT"
-%foo_name = OpString "foo"
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%VS_OUTPUT_info = OpExtInst %void %DbgExt DebugTypeComposite %VS_OUTPUT_name Structure %dbg_src 1 1 %comp_unit %VS_OUTPUT_name %u32_0 FlagIsPublic %child
-%foo_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Structure %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
-%bar_info = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Union %dbg_src 1 1 %comp_unit %foo_name %u32_0 FlagIsPublic
-%child = OpExtInst %void %DbgExt DebugTypeInheritance )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugTypeInheritance,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(R"(%dbg_src %VS_OUTPUT_info %u32_0 %u32_0 FlagIsPublic)",
-                       "Child must be a result id of"),
-        std::make_pair(R"(%foo_info %dbg_src %u32_0 %u32_0 FlagIsPublic)",
-                       "Parent must be a result id of"),
-        std::make_pair(
-            R"(%bar_info %VS_OUTPUT_info %u32_0 %u32_0 FlagIsPublic)",
-            "Child must be class or struct debug type"),
-        std::make_pair(R"(%foo_info %bar_info %u32_0 %u32_0 FlagIsPublic)",
-                       "Parent must be class or struct debug type"),
-        std::make_pair(R"(%foo_info %VS_OUTPUT_info %void %u32_0 FlagIsPublic)",
-                       "Offset"),
-        std::make_pair(R"(%foo_info %VS_OUTPUT_info %u32_0 %void FlagIsPublic)",
-                       "Size"),
-    }));
 TEST_P(ValidateGlslStd450SqrtLike, Success) {
   const std::string ext_inst_name = GetParam();
   std::ostringstream ss;
@@ -2178,1086 +472,6 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
-TEST_F(ValidateOpenCL100DebugInfo, DebugFunctionDeclaration) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float4 pos : SV_POSITION;
-};
-main() {}
-"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v4f_main_f"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugFunction, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float4 pos : SV_POSITION;
-};
-main() {}
-"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v4f_main_f"
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration %main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic
-%main_info = OpExtInst %void %DbgExt DebugFunction )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugFunction,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(
-            R"(%u32_0 %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
-            "Name"),
-        std::make_pair(
-            R"(%main_name %dbg_src %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
-            "Type"),
-        std::make_pair(
-            R"(%main_name %main_type_info %comp_unit 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main)",
-            "Source"),
-        std::make_pair(
-            R"(%main_name %main_type_info %dbg_src 12 1 %dbg_src %main_linkage_name FlagIsPublic 13 %main)",
-            "Parent"),
-        std::make_pair(
-            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %void FlagIsPublic 13 %main)",
-            "Linkage Name"),
-        std::make_pair(
-            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %void)",
-            "Function"),
-        std::make_pair(
-            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic 13 %main %dbg_src)",
-            "Declaration"),
-    }));
-
-TEST_P(ValidateOpenCL100DebugInfoDebugFunctionDeclaration, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "struct VS_OUTPUT {
-  float4 pos : SV_POSITION;
-};
-main() {}
-"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v4f_main_f"
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_decl = OpExtInst %void %DbgExt DebugFunctionDeclaration )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail,
-    ValidateOpenCL100DebugInfoDebugFunctionDeclaration,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(
-            R"(%u32_0 %main_type_info %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
-            "Name"),
-        std::make_pair(
-            R"(%main_name %dbg_src %dbg_src 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
-            "Type"),
-        std::make_pair(
-            R"(%main_name %main_type_info %comp_unit 12 1 %comp_unit %main_linkage_name FlagIsPublic)",
-            "Source"),
-        std::make_pair(
-            R"(%main_name %main_type_info %dbg_src 12 1 %dbg_src %main_linkage_name FlagIsPublic)",
-            "Parent"),
-        std::make_pair(
-            R"(%main_name %main_type_info %dbg_src 12 1 %comp_unit %void FlagIsPublic)",
-            "Linkage Name"),
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugLexicalBlock) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%main_name = OpString "main"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_block = OpExtInst %void %DbgExt DebugLexicalBlock %dbg_src 1 1 %comp_unit %main_name)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", dbg_inst_header,
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugLexicalBlock, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "main() {}"
-%main_name = OpString "main"
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_block = OpExtInst %void %DbgExt DebugLexicalBlock )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", ss.str(), "",
-                                                     extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugLexicalBlock,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(R"(%comp_unit 1 1 %comp_unit %main_name)", "Source"),
-        std::make_pair(R"(%dbg_src 1 1 %dbg_src %main_name)", "Parent"),
-        std::make_pair(R"(%dbg_src 1 1 %comp_unit %void)", "Name"),
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugScopeFailScope) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %dbg_src
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugScopeFailInlinedAt) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %comp_unit %dbg_src
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined At"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugLocalVariable) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() { float foo; }"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugLocalVariable, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() { float foo; }"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo = OpExtInst %void %DbgExt DebugLocalVariable )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugLocalVariable,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(
-            R"(%void %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0)",
-            "Name"),
-        std::make_pair(
-            R"(%foo_name %dbg_src %dbg_src 1 10 %comp_unit FlagIsLocal 0)",
-            "Type"),
-        std::make_pair(
-            R"(%foo_name %float_info %comp_unit 1 10 %comp_unit FlagIsLocal 0)",
-            "Source"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 1 10 %dbg_src FlagIsLocal 0)",
-            "Parent"),
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugDeclare) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() { float foo; }"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%null_expr = OpExtInst %void %DbgExt DebugExpression
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
-)";
-
-  const std::string body = R"(
-%foo = OpVariable %f32_ptr_function Function
-%decl = OpExtInst %void %DbgExt DebugDeclare %foo_info %foo %null_expr
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugDeclareParam) {
-  CompileSuccessfully(R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Vertex %main "main" %in_var_COLOR
-          %4 = OpString "test.hlsl"
-               OpSource HLSL 620 %4 "#line 1 \"test.hlsl\"
-void main(float foo:COLOR) {}
-"
-         %11 = OpString "#line 1 \"test.hlsl\"
-void main(float foo:COLOR) {}
-"
-         %14 = OpString "float"
-         %17 = OpString "src.main"
-         %20 = OpString "foo"
-               OpName %in_var_COLOR "in.var.COLOR"
-               OpName %main "main"
-               OpName %param_var_foo "param.var.foo"
-               OpName %src_main "src.main"
-               OpName %foo "foo"
-               OpName %bb_entry "bb.entry"
-               OpDecorate %in_var_COLOR Location 0
-       %uint = OpTypeInt 32 0
-    %uint_32 = OpConstant %uint 32
-      %float = OpTypeFloat 32
-%_ptr_Input_float = OpTypePointer Input %float
-       %void = OpTypeVoid
-         %23 = OpTypeFunction %void
-%_ptr_Function_float = OpTypePointer Function %float
-         %29 = OpTypeFunction %void %_ptr_Function_float
-               OpLine %4 1 21
-%in_var_COLOR = OpVariable %_ptr_Input_float Input
-         %10 = OpExtInst %void %1 DebugExpression
-         %12 = OpExtInst %void %1 DebugSource %4 %11
-         %13 = OpExtInst %void %1 DebugCompilationUnit 1 4 %12 HLSL
-         %15 = OpExtInst %void %1 DebugTypeBasic %14 %uint_32 Float
-         %16 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %void %15
-         %18 = OpExtInst %void %1 DebugFunction %17 %16 %12 1 1 %13 %17 FlagIsProtected|FlagIsPrivate 1 %src_main
-         %21 = OpExtInst %void %1 DebugLocalVariable %20 %15 %12 1 17 %18 FlagIsLocal 0
-         %22 = OpExtInst %void %1 DebugLexicalBlock %12 1 28 %18
-               OpLine %4 1 1
-       %main = OpFunction %void None %23
-         %24 = OpLabel
-               OpLine %4 1 17
-%param_var_foo = OpVariable %_ptr_Function_float Function
-         %27 = OpLoad %float %in_var_COLOR
-               OpLine %4 1 1
-         %28 = OpFunctionCall %void %src_main %param_var_foo
-               OpReturn
-               OpFunctionEnd
-   %src_main = OpFunction %void None %29
-               OpLine %4 1 17
-        %foo = OpFunctionParameter %_ptr_Function_float
-         %31 = OpExtInst %void %1 DebugDeclare %21 %foo %10
-   %bb_entry = OpLabel
-               OpLine %4 1 29
-               OpReturn
-               OpFunctionEnd
-)");
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugDeclare, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() { float foo; }"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%null_expr = OpExtInst %void %DbgExt DebugExpression
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%foo = OpVariable %f32_ptr_function Function
-%decl = OpExtInst %void %DbgExt DebugDeclare )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, ss.str(), extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugDeclare,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(R"(%dbg_src %foo %null_expr)", "Local Variable"),
-        std::make_pair(R"(%foo_info %void %null_expr)", "Variable"),
-        std::make_pair(R"(%foo_info %foo %dbg_src)", "Expression"),
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugExpression) {
-  const std::string dbg_inst_header = R"(
-%op0 = OpExtInst %void %DbgExt DebugOperation Deref
-%op1 = OpExtInst %void %DbgExt DebugOperation Plus
-%null_expr = OpExtInst %void %DbgExt DebugExpression %op0 %op1
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugExpressionFail) {
-  const std::string dbg_inst_header = R"(
-%op = OpExtInst %void %DbgExt DebugOperation Deref
-%null_expr = OpExtInst %void %DbgExt DebugExpression %op %void
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header,
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "expected operand Operation must be a result id of DebugOperation"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplate) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "OpaqueType foo;
-main() {}
-"
-%float_name = OpString "float"
-%ty_name = OpString "Texture"
-%t_name = OpString "T"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic
-%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
-%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateUsedForVariableType) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "OpaqueType foo;
-main() {}
-"
-%float_name = OpString "float"
-%ty_name = OpString "Texture"
-%t_name = OpString "T"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%opaque = OpExtInst %void %DbgExt DebugTypeComposite %ty_name Class %dbg_src 1 1 %comp_unit %ty_name %dbg_none FlagIsPublic
-%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
-%temp = OpExtInst %void %DbgExt DebugTypeTemplate %opaque %param
-%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %temp %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFunction) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "OpaqueType foo;
-main() {}
-"
-%float_name = OpString "float"
-%ty_name = OpString "Texture"
-%t_name = OpString "T"
-%main_name = OpString "main"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %param %param
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
-%temp = OpExtInst %void %DbgExt DebugTypeTemplate %main_info %param
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFailTarget) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "OpaqueType foo;
-main() {}
-"
-%float_name = OpString "float"
-%ty_name = OpString "Texture"
-%t_name = OpString "T"
-%main_name = OpString "main"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
-%temp = OpExtInst %void %DbgExt DebugTypeTemplate %float_info %param
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Target must be DebugTypeComposite or "
-                        "DebugFunction"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugTypeTemplateFailParam) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "OpaqueType foo;
-main() {}
-"
-%float_name = OpString "float"
-%ty_name = OpString "Texture"
-%t_name = OpString "T"
-%main_name = OpString "main"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-%int_128 = OpConstant %u32 128
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_none = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%param = OpExtInst %void %DbgExt DebugTypeTemplateParameter %t_name %float_info %dbg_none %dbg_src 0 0
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %param %param
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_name FlagIsPublic 1 %main
-%temp = OpExtInst %void %DbgExt DebugTypeTemplate %main_info %float_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr(
-          "expected operand Parameters must be DebugTypeTemplateParameter or "
-          "DebugTypeTemplateTemplateParameter"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariable) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float foo; void main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableStaticMember) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float foo; void main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%t = OpExtInst %void %DbgExt DebugTypeComposite %foo_name Class %dbg_src 0 0 %comp_unit %foo_name %int_32 FlagIsPublic %a
-%a = OpExtInst %void %DbgExt DebugTypeMember %foo_name %float_info %dbg_src 0 0 %t %u32_0 %int_32 FlagIsPublic
-%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate %a
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableDebugInfoNone) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float foo; void main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbgNone = OpExtInst %void %DbgExt DebugInfoNone
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %dbgNone FlagIsProtected|FlagIsPrivate
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugGlobalVariableConst) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float foo; void main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo = OpExtInst %void %DbgExt DebugGlobalVariable %foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %int_32 FlagIsProtected|FlagIsPrivate
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, "", extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugGlobalVariable, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "float foo; void main() {}"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo = OpExtInst %void %DbgExt DebugGlobalVariable )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, size_const, ss.str(),
-                                                     "", extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugGlobalVariable,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(
-            R"(%void %float_info %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
-            "Name"),
-        std::make_pair(
-            R"(%foo_name %dbg_src %dbg_src 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
-            "Type"),
-        std::make_pair(
-            R"(%foo_name %float_info %comp_unit 0 0 %comp_unit %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
-            "Source"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 0 0 %dbg_src %foo_name %f32_input FlagIsProtected|FlagIsPrivate)",
-            "Scope"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 0 0 %comp_unit %void %f32_input FlagIsProtected|FlagIsPrivate)",
-            "Linkage Name"),
-        std::make_pair(
-            R"(%foo_name %float_info %dbg_src 0 0 %comp_unit %foo_name %void FlagIsProtected|FlagIsPrivate)",
-            "Variable"),
-    }));
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAt) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-%void_name = OpString "void"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
-%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info
-%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info %inlined_at
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAtFail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-%void_name = OpString "void"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
-%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info
-%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %inlined_at
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Scope"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugInlinedAtFail2) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() {}"
-%void_name = OpString "void"
-%main_name = OpString "main"
-%main_linkage_name = OpString "v_main"
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void
-%main_info = OpExtInst %void %DbgExt DebugFunction %main_name %main_type_info %dbg_src 1 1 %comp_unit %main_linkage_name FlagIsPublic 1 %main
-%inlined_at = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info
-%inlined_at_recursive = OpExtInst %void %DbgExt DebugInlinedAt 0 %main_info %main_info
-)";
-
-  const std::string body = R"(
-%main_scope = OpExtInst %void %DbgExt DebugScope %main_info %inlined_at
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, "", dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("expected operand Inlined"));
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugValue) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() { float foo; }"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_3 = OpConstant %u32 3
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%null_expr = OpExtInst %void %DbgExt DebugExpression
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
-)";
-
-  const std::string body = R"(
-%value = OpExtInst %void %DbgExt DebugValue %foo_info %int_32 %null_expr %int_3
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_F(ValidateOpenCL100DebugInfo, DebugValueWithVariableIndex) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() { float foo; }"
-%float_name = OpString "float"
-%int_name = OpString "int"
-%foo_name = OpString "foo"
-%len_name = OpString "length"
-)";
-
-  const std::string size_const = R"(
-%int_3 = OpConstant %u32 3
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%null_expr = OpExtInst %void %DbgExt DebugExpression
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %int_32 Signed
-%v4float_info = OpExtInst %void %DbgExt DebugTypeVector %float_info 4
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %v4float_info %dbg_src 1 10 %comp_unit FlagIsLocal
-%len_info = OpExtInst %void %DbgExt DebugLocalVariable %len_name %int_info %dbg_src 0 0 %comp_unit FlagIsLocal
-)";
-
-  const std::string body = R"(
-%value = OpExtInst %void %DbgExt DebugValue %foo_info %int_32 %null_expr %len_info
-)";
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, body, extension, "Vertex"));
-  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
-}
-
-TEST_P(ValidateOpenCL100DebugInfoDebugValue, Fail) {
-  const std::string src = R"(
-%src = OpString "simple.hlsl"
-%code = OpString "void main() { float foo; }"
-%float_name = OpString "float"
-%foo_name = OpString "foo"
-)";
-
-  const std::string size_const = R"(
-%int_32 = OpConstant %u32 32
-)";
-
-  const std::string dbg_inst_header = R"(
-%dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
-%comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%null_expr = OpExtInst %void %DbgExt DebugExpression
-%float_info = OpExtInst %void %DbgExt DebugTypeBasic %float_name %int_32 Float
-%foo_info = OpExtInst %void %DbgExt DebugLocalVariable %foo_name %float_info %dbg_src 1 10 %comp_unit FlagIsLocal 0
-)";
-
-  const auto& param = GetParam();
-
-  std::ostringstream ss;
-  ss << R"(
-%decl = OpExtInst %void %DbgExt DebugValue )"
-     << param.first;
-
-  const std::string extension = R"(
-%DbgExt = OpExtInstImport "OpenCL.DebugInfo.100"
-)";
-
-  CompileSuccessfully(GenerateShaderCodeForDebugInfo(
-      src, size_const, dbg_inst_header, ss.str(), extension, "Vertex"));
-  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand " + param.second));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AllOpenCL100DebugInfoFail, ValidateOpenCL100DebugInfoDebugValue,
-    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
-        std::make_pair(R"(%dbg_src %int_32 %null_expr)", "Local Variable"),
-        std::make_pair(R"(%foo_info %int_32 %dbg_src)", "Expression"),
-        std::make_pair(R"(%foo_info %int_32 %null_expr %dbg_src)", "Indexes"),
-    }));
-
 TEST_P(ValidateGlslStd450SqrtLike, IntResultType) {
   const std::string ext_inst_name = GetParam();
   const std::string body =
@@ -3507,6 +721,19 @@
                         "Result Type"));
 }
 
+TEST_P(ValidateGlslStd450SAbsLike, TypelessOperand) {
+  const std::string ext_inst_name = GetParam();
+  const std::string body =
+      "%val1 = OpExtInst %s64 %extinst " + ext_inst_name + " %main_entry\n";
+
+  CompileSuccessfully(GenerateShaderCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("GLSL.std.450 " + ext_inst_name +
+                ": expected all operands to be int scalars or vectors"));
+}
+
 INSTANTIATE_TEST_SUITE_P(AllSAbsLike, ValidateGlslStd450SAbsLike,
                          ::testing::ValuesIn(std::vector<std::string>{
                              "SAbs",
@@ -3656,6 +883,19 @@
                         "Result Type"));
 }
 
+TEST_P(ValidateGlslStd450UMinLike, TypelessOperand) {
+  const std::string ext_inst_name = GetParam();
+  const std::string body = "%val1 = OpExtInst %s64 %extinst " + ext_inst_name +
+                           " %s64_0 %main_entry\n";
+
+  CompileSuccessfully(GenerateShaderCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("GLSL.std.450 " + ext_inst_name +
+                ": expected all operands to be int scalars or vectors"));
+}
+
 INSTANTIATE_TEST_SUITE_P(AllUMinLike, ValidateGlslStd450UMinLike,
                          ::testing::ValuesIn(std::vector<std::string>{
                              "UMin",
@@ -3819,6 +1059,19 @@
                         "Result Type"));
 }
 
+TEST_P(ValidateGlslStd450UClampLike, TypelessOperand) {
+  const std::string ext_inst_name = GetParam();
+  const std::string body = "%val1 = OpExtInst %s64 %extinst " + ext_inst_name +
+                           " %main_entry %s64_0 %s64_0\n";
+
+  CompileSuccessfully(GenerateShaderCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("GLSL.std.450 " + ext_inst_name +
+                ": expected all operands to be int scalars or vectors"));
+}
+
 INSTANTIATE_TEST_SUITE_P(AllUClampLike, ValidateGlslStd450UClampLike,
                          ::testing::ValuesIn(std::vector<std::string>{
                              "UClamp",
@@ -4327,6 +1580,19 @@
                         "number as Result Type"));
 }
 
+TEST_F(ValidateExtInst, GlslStd450LdexpExpNoType) {
+  const std::string body = R"(
+%val1 = OpExtInst %f32 %extinst Ldexp %f32_1 %main_entry
+)";
+
+  CompileSuccessfully(GenerateShaderCode(body));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("GLSL.std.450 Ldexp: "
+                        "expected operand Exp to be a 32-bit int scalar "
+                        "or vector type"));
+}
+
 TEST_F(ValidateExtInst, GlslStd450FrexpStructSuccess) {
   const std::string body = R"(
 %val1 = OpExtInst %struct_f32_u32 %extinst FrexpStruct %f32_h
@@ -5204,6 +2470,49 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidInternalSuccess) {
+  const std::string body = R"(
+%ld1  = OpLoad %f32 %f32_input
+%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %ld1
+%ld2  = OpLoad %f32vec2 %f32vec2_input
+%val2 = OpExtInst %f32vec2 %extinst InterpolateAtCentroid %ld2
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  getValidatorOptions()->before_hlsl_legalization = true;
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidInternalInvalidDataF32) {
+  const std::string body = R"(
+%ld1  = OpLoad %f32 %f32_input
+%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %ld1
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("GLSL.std.450 InterpolateAtCentroid: "
+                        "expected Interpolant to be a pointer"));
+}
+
+TEST_F(ValidateExtInst,
+       GlslStd450InterpolateAtCentroidInternalInvalidDataF32Vec2) {
+  const std::string body = R"(
+%ld2  = OpLoad %f32vec2 %f32vec2_input
+%val2 = OpExtInst %f32vec2 %extinst InterpolateAtCentroid %ld2
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("GLSL.std.450 InterpolateAtCentroid: "
+                        "expected Interpolant to be a pointer"));
+}
+
 TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidNoCapability) {
   const std::string body = R"(
 %val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %f32_input
@@ -5308,6 +2617,49 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleInternalSuccess) {
+  const std::string body = R"(
+%ld1  = OpLoad %f32 %f32_input
+%val1 = OpExtInst %f32 %extinst InterpolateAtSample %ld1 %u32_1
+%ld2  = OpLoad %f32vec2 %f32vec2_input
+%val2 = OpExtInst %f32vec2 %extinst InterpolateAtSample %ld2 %u32_1
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  getValidatorOptions()->before_hlsl_legalization = true;
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleInternalInvalidDataF32) {
+  const std::string body = R"(
+%ld1  = OpLoad %f32 %f32_input
+%val1 = OpExtInst %f32 %extinst InterpolateAtSample %ld1 %u32_1
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("GLSL.std.450 InterpolateAtSample: "
+                        "expected Interpolant to be a pointer"));
+}
+
+TEST_F(ValidateExtInst,
+       GlslStd450InterpolateAtSampleInternalInvalidDataF32Vec2) {
+  const std::string body = R"(
+%ld2  = OpLoad %f32vec2 %f32vec2_input
+%val2 = OpExtInst %f32vec2 %extinst InterpolateAtSample %ld2 %u32_1
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("GLSL.std.450 InterpolateAtSample: "
+                        "expected Interpolant to be a pointer"));
+}
+
 TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleNoCapability) {
   const std::string body = R"(
 %val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_input %u32_1
@@ -5438,6 +2790,49 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetInternalSuccess) {
+  const std::string body = R"(
+%ld1  = OpLoad %f32 %f32_input
+%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %ld1 %f32vec2_01
+%ld2  = OpLoad %f32vec2 %f32vec2_input
+%val2 = OpExtInst %f32vec2 %extinst InterpolateAtOffset %ld2 %f32vec2_01
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  getValidatorOptions()->before_hlsl_legalization = true;
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetInternalInvalidDataF32) {
+  const std::string body = R"(
+%ld1  = OpLoad %f32 %f32_input
+%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %ld1 %f32vec2_01
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("GLSL.std.450 InterpolateAtOffset: "
+                        "expected Interpolant to be a pointer"));
+}
+
+TEST_F(ValidateExtInst,
+       GlslStd450InterpolateAtOffsetInternalInvalidDataF32Vec2) {
+  const std::string body = R"(
+%ld2  = OpLoad %f32vec2 %f32vec2_input
+%val2 = OpExtInst %f32vec2 %extinst InterpolateAtOffset %ld2 %f32vec2_01
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "OpCapability InterpolationFunction\n"));
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("GLSL.std.450 InterpolateAtOffset: "
+                        "expected Interpolant to be a pointer"));
+}
+
 TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetNoCapability) {
   const std::string body = R"(
 %val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %f32vec2_01
diff --git a/test/val/val_extension_spv_khr_bit_instructions.cpp b/test/val/val_extension_spv_khr_bit_instructions.cpp
new file mode 100644
index 0000000..0e92671
--- /dev/null
+++ b/test/val/val_extension_spv_khr_bit_instructions.cpp
@@ -0,0 +1,117 @@
+// Copyright (c) 2021 The Khronos Group Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Tests for OpExtension validator rules.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/enum_string_mapping.h"
+#include "source/extensions.h"
+#include "source/spirv_target_env.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+using ValidateSpvKHRBitInstructions = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateSpvKHRBitInstructions, Valid) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability BitInstructions
+    OpExtension "SPV_KHR_bit_instructions"
+    OpMemoryModel Physical32 OpenCL
+    OpEntryPoint Kernel %main "main"
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+    %u32 = OpTypeInt 32 0
+    %u32_1 = OpConstant %u32 1
+
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+    %unused = OpBitReverse %u32 %u32_1
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateSpvKHRBitInstructions, RequiresExtension) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability BitInstructions
+    OpMemoryModel Physical32 OpenCL
+    OpEntryPoint Kernel %main "main"
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+    %u32 = OpTypeInt 32 0
+    %u32_1 = OpConstant %u32 1
+
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+    %unused = OpBitReverse %u32 %u32_1
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("1st operand of Capability: operand BitInstructions(6025) "
+                "requires one of these extensions: SPV_KHR_bit_instructions"));
+}
+
+TEST_F(ValidateSpvKHRBitInstructions, RequiresCapability) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpExtension "SPV_KHR_bit_instructions"
+    OpMemoryModel Physical32 OpenCL
+    OpEntryPoint Kernel %main "main"
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+    %u32 = OpTypeInt 32 0
+    %u32_1 = OpConstant %u32 1
+
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+    %unused = OpBitReverse %u32 %u32_1
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Opcode BitReverse requires one of these capabilities: "
+                        "Shader BitInstructions"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_extension_spv_khr_expect_assume.cpp b/test/val/val_extension_spv_khr_expect_assume.cpp
new file mode 100644
index 0000000..6ece15d
--- /dev/null
+++ b/test/val/val_extension_spv_khr_expect_assume.cpp
@@ -0,0 +1,310 @@
+// Copyright (c) 2020 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 OpExtension validator rules.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/enum_string_mapping.h"
+#include "source/extensions.h"
+#include "source/spirv_target_env.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+using ValidateSpvExpectAssumeKHR = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateSpvExpectAssumeKHR, Valid) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpCapability ExpectAssumeKHR
+    OpExtension "SPV_KHR_expect_assume"
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %true = OpConstantTrue %bool
+    %undef = OpUndef %bool
+
+    %uint = OpTypeInt 32 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+
+    %v2bool = OpTypeVector %bool 2
+    %v2uint = OpTypeVector %uint 2
+
+    %null_v2bool = OpConstantNull %v2bool
+    %null_v2uint = OpConstantNull %v2uint
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpAssumeTrueKHR %true
+    OpAssumeTrueKHR %undef       ; probably undefined behaviour
+    %bool_val = OpExpectKHR %bool %true %true
+    %uint_val = OpExpectKHR %uint %uint_1 %uint_2 ; a bad expectation
+    %v2bool_val = OpExpectKHR %v2bool %null_v2bool %null_v2bool
+    %v2uint_val = OpExpectKHR %v2uint %null_v2uint %null_v2uint
+    OpReturn
+    OpFunctionEnd
+
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateSpvExpectAssumeKHR, RequiresExtension) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpCapability ExpectAssumeKHR
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %true = OpConstantTrue %bool
+    %undef = OpUndef %bool
+
+    %uint = OpTypeInt 32 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpAssumeTrueKHR %true
+    OpAssumeTrueKHR %undef       ; probably undefined behaviour
+    %val = OpExpectKHR %uint %uint_1 %uint_2 ; a bad expectation
+    OpReturn
+    OpFunctionEnd
+
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Capability: operand ExpectAssumeKHR(5629) requires "
+                        "one of these extensions: SPV_KHR_expect_assume"));
+}
+
+TEST_F(ValidateSpvExpectAssumeKHR,
+       AssumeTrueKHR_RequiresExpectAssumeCapability) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpExtension "SPV_KHR_expect_assume"
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %true = OpConstantTrue %bool
+    %undef = OpUndef %bool
+
+    %uint = OpTypeInt 32 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpAssumeTrueKHR %true
+    OpAssumeTrueKHR %undef       ; probably undefined behaviour
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Opcode AssumeTrueKHR requires one of these "
+                        "capabilities: ExpectAssumeKHR \n"
+                        "  OpAssumeTrueKHR %true\n"));
+}
+
+TEST_F(ValidateSpvExpectAssumeKHR, AssumeTrueKHR_OperandMustBeBool) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpCapability ExpectAssumeKHR
+    OpExtension "SPV_KHR_expect_assume"
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %true = OpConstantTrue %bool
+    %undef = OpUndef %bool
+
+    %uint = OpTypeInt 32 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    OpAssumeTrueKHR %uint_1 ; bad type
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Value operand of OpAssumeTrueKHR must be a boolean scalar\n"
+                "  OpAssumeTrueKHR %uint_1\n"));
+}
+
+TEST_F(ValidateSpvExpectAssumeKHR, ExpectKHR_RequiresExpectAssumeCapability) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpExtension "SPV_KHR_expect_assume"
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %bool = OpTypeBool
+    %true = OpConstantTrue %bool
+    %undef = OpUndef %bool
+
+    %uint = OpTypeInt 32 0
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %val = OpExpectKHR %uint %uint_1 %uint_2 ; a bad expectation
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Opcode ExpectKHR requires one of these capabilities: "
+                        "ExpectAssumeKHR \n"
+                        "  %11 = OpExpectKHR %uint %uint_1 %uint_2\n"));
+}
+
+TEST_F(ValidateSpvExpectAssumeKHR, ExpectKHR_ResultMustBeBoolOrIntScalar) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpCapability ExpectAssumeKHR
+    OpExtension "SPV_KHR_expect_assume"
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %float = OpTypeFloat 32
+
+    %float_0 = OpConstant %float 0
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %val = OpExpectKHR %float %float_0 %float_0
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Result of OpExpectKHR must be a scalar or vector of "
+                        "integer type or boolean type\n"
+                        "  %7 = OpExpectKHR %float %float_0 %float_0\n"));
+}
+
+TEST_F(ValidateSpvExpectAssumeKHR, ExpectKHR_Value0MustMatchResultType) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpCapability ExpectAssumeKHR
+    OpExtension "SPV_KHR_expect_assume"
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %uint = OpTypeInt 32 0
+    %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %val = OpExpectKHR %uint %float_0 %float_0
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Type of Value operand of OpExpectKHR does not match "
+                        "the result type \n"
+                        "  %8 = OpExpectKHR %uint %float_0 %float_0\n"));
+}
+
+TEST_F(ValidateSpvExpectAssumeKHR, ExpectKHR_Value1MustMatchResultType) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpCapability ExpectAssumeKHR
+    OpExtension "SPV_KHR_expect_assume"
+    OpMemoryModel Physical32 OpenCL
+
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %uint = OpTypeInt 32 0
+    %uint_0 = OpConstant %uint 0
+    %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+
+    %main = OpFunction %void None %voidfn
+    %entry = OpLabel
+    %val = OpExpectKHR %uint %uint_0 %float_0
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Type of ExpectedValue operand of OpExpectKHR does not "
+                        "match the result type \n"
+                        "  %9 = OpExpectKHR %uint %uint_0 %float_0\n"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_extension_spv_khr_integer_dot_product.cpp b/test/val/val_extension_spv_khr_integer_dot_product.cpp
new file mode 100644
index 0000000..e0e6896
--- /dev/null
+++ b/test/val/val_extension_spv_khr_integer_dot_product.cpp
@@ -0,0 +1,1330 @@
+// Copyright (c) 2020 Google Inc.
+// Copyright (c) 2021 Arm Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/enum_string_mapping.h"
+#include "source/extensions.h"
+#include "source/spirv_target_env.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+
+struct Case {
+  std::vector<std::string> caps;
+  std::string inst;
+  std::string result_type;
+  std::string op0_type;
+  std::string op1_type;
+  std::string acc_type;  // can be empty
+  bool packed;
+  std::string expected_error;  // empty for no error.
+};
+
+inline std::ostream& operator<<(std::ostream& out, Case c) {
+  out << "\nSPV_KHR_integer_dot_product Case{{";
+  bool first = true;
+  for (const auto& cap : c.caps) {
+    if (!first) {
+      out << " ";
+    }
+    first = false;
+    out << cap;
+  }
+  out << "} ";
+  out << c.inst << " ";
+  out << c.result_type << " ";
+  out << c.op0_type << " ";
+  out << c.op1_type << " ";
+  out << "'" << c.acc_type << "' ";
+  out << (c.packed ? "packed " : "unpacked ");
+  out << "err'" << c.expected_error << "'";
+  return out;
+}
+
+std::string AssemblyForCase(const Case& c) {
+  std::ostringstream ss;
+  ss << "OpCapability Shader\n";
+  for (auto& cap : c.caps) {
+    ss << "OpCapability " << cap << "\n";
+  }
+  ss << R"(
+  OpExtension "SPV_KHR_integer_dot_product"
+  OpMemoryModel Logical Simple
+  OpEntryPoint GLCompute %main "main"
+  OpExecutionMode %main LocalSize 1 1 1
+
+  %void = OpTypeVoid
+  %voidfn = OpTypeFunction %void
+  %uint = OpTypeInt 32 0
+  %int = OpTypeInt 32 1
+
+  %v2uint = OpTypeVector %uint 2
+  %v3uint = OpTypeVector %uint 3
+  %v4uint = OpTypeVector %uint 4
+  %v2int = OpTypeVector %int 2
+  %v3int = OpTypeVector %int 3
+  %v4int = OpTypeVector %int 4
+
+  %uint_0 = OpConstant %uint 0
+  %uint_1 = OpConstant %uint 1
+  %int_0 = OpConstant %int 0
+  %int_1 = OpConstant %int 1
+
+  %v2uint_0 = OpConstantComposite %v2uint %uint_0 %uint_0
+  %v2uint_1 = OpConstantComposite %v2uint %uint_1 %uint_1
+  %v3uint_0 = OpConstantComposite %v3uint %uint_0 %uint_0 %uint_0
+  %v3uint_1 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
+  %v4uint_0 = OpConstantComposite %v4uint %uint_0 %uint_0 %uint_0 %uint_0
+  %v4uint_1 = OpConstantComposite %v4uint %uint_1 %uint_1 %uint_1 %uint_1
+
+  %v2int_0 = OpConstantComposite %v2int %int_0 %int_0
+  %v2int_1 = OpConstantComposite %v2int %int_1 %int_1
+  %v3int_0 = OpConstantComposite %v3int %int_0 %int_0 %int_0
+  %v3int_1 = OpConstantComposite %v3int %int_1 %int_1 %int_1
+  %v4int_0 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0
+  %v4int_1 = OpConstantComposite %v4int %int_1 %int_1 %int_1 %int_1
+)";
+
+  bool use8bit = false;
+  for (auto& cap : c.caps) {
+    if (cap == "DotProductInput4x8BitKHR") {
+      use8bit = true;
+    }
+    if (cap == "Int8") {
+      use8bit = true;
+    }
+  }
+  if (use8bit) {
+    ss << R"(
+         %uchar = OpTypeInt 8 0
+         %char = OpTypeInt 8 1
+
+         %v4uchar = OpTypeVector %uchar 4
+         %v4char = OpTypeVector %char 4
+
+         %uchar_0 = OpConstant %uchar 0
+         %uchar_1 = OpConstant %uchar 1
+         %char_0 = OpConstant %char 0
+         %char_1 = OpConstant %char 1
+
+         %v4uchar_0 = OpConstantComposite %v4uchar %uchar_0 %uint_0 %uchar_0 %uchar_0
+         %v4uchar_1 = OpConstantComposite %v4uchar %uchar_1 %uchar_1 %uchar_1 %uchar_1
+         %v4char_0 = OpConstantComposite %v4char %char_0 %char_0 %char_0 %char_0
+         %v4char_1 = OpConstantComposite %v4char %char_1 %char_1 %char_1 %char_1
+
+         )";
+  }
+
+  ss << R"(
+
+  %main = OpFunction %void None %voidfn
+  %entry = OpLabel
+  %result = )"
+     << c.inst << " " << c.result_type << " ";
+  ss << c.op0_type << "_0 ";
+  ss << c.op1_type << "_1 ";
+  if (!c.acc_type.empty()) {
+    ss << c.acc_type << "_0 ";
+  }
+  if (c.packed) {
+    ss << "PackedVectorFormat4x8BitKHR";
+  }
+  ss << "\nOpReturn\nOpFunctionEnd\n\n";
+  return ss.str();
+}
+
+using ValidateSpvKHRIntegerDotProduct = spvtest::ValidateBase<Case>;
+
+TEST_P(ValidateSpvKHRIntegerDotProduct, Valid) {
+  const auto& c = GetParam();
+  const auto& assembly = AssemblyForCase(c);
+  CompileSuccessfully(assembly);
+  if (c.expected_error.empty()) {
+    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
+  } else {
+    EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(c.expected_error));
+  }
+}
+
+// UDot
+INSTANTIATE_TEST_SUITE_P(
+    Valid_UDot, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpUDotKHR",
+                           "%uint",
+                           "%v2uint",
+                           "%v2uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpUDotKHR",
+                           "%uint",
+                           "%v3uint",
+                           "%v3uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpUDotKHR",
+                           "%uint",
+                           "%v4uint",
+                           "%v4uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpUDotKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpUDotKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpUDotKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpUDotKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpUDotKHR",
+                           "%uchar",  // matches packed component type
+                           "%uint",
+                           "%uint",
+                           "",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpUDotKHR",
+                           "%uint",
+                           "%uint",
+                           "%uint",
+                           "",
+                           true,
+                           ""}));
+
+// SDot result signed args signed signed
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SDot_signed_signed_signed, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%v2int",
+                           "%v2int",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%v3int",
+                           "%v3int",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%v4int",
+                           "%v4int",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4char",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4char",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4char",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4char",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSDotKHR",
+                           "%char",  // matches packed component type
+                           "%int",
+                           "%int",
+                           "",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%int",
+                           "%int",
+                           "",
+                           true,
+                           ""}));
+
+// SDot result unsigned args signed unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SDot_unsigned_signed_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%uint",
+                           "%v2int",
+                           "%v2uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%uint",
+                           "%v3int",
+                           "%v3uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%uint",
+                           "%v4int",
+                           "%v4uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotKHR",
+                           "%uchar",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotKHR",
+                           "%uint",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotKHR",
+                           "%uchar",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotKHR",
+                           "%uint",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSDotKHR",
+                           "%uchar",  // matches packed component type
+                           "%int",
+                           "%uint",
+                           "",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSDotKHR",
+                           "%uint",
+                           "%int",
+                           "%uint",
+                           "",
+                           true,
+                           ""}));
+
+// SDot result signed args signed unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SDot_signed_signed_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%v2int",
+                           "%v2uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%v3int",
+                           "%v3uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%v4int",
+                           "%v4uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSDotKHR",
+                           "%char",  // matches packed component type
+                           "%int",
+                           "%uint",
+                           "",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSDotKHR",
+                           "%int",
+                           "%int",
+                           "%uint",
+                           "",
+                           true,
+                           ""}));
+
+// SUDot result signed args unsigned unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SUDot_signed_unsigned_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%v2uint",
+                           "%v2uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%v3uint",
+                           "%v3uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%v4uint",
+                           "%v4uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotKHR",
+                           "%char",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotKHR",
+                           "%int",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotKHR",
+                           "%char",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotKHR",
+                           "%int",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSUDotKHR",
+                           "%char",  // matches packed component type
+                           "%uint",
+                           "%uint",
+                           "",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%uint",
+                           "%uint",
+                           "",
+                           true,
+                           ""}));
+
+// SUDot result signed args signed unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SUDot_signed_signed_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%v2int",
+                           "%v2uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%v3int",
+                           "%v3uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%v4int",
+                           "%v4uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSUDotKHR",
+                           "%char",  // matches packed component type
+                           "%int",
+                           "%uint",
+                           "",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSUDotKHR",
+                           "%int",
+                           "%int",
+                           "%uint",
+                           "",
+                           true,
+                           ""}));
+
+// SUDot result unsigned args unsigned unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SUDot_unsigned_unsigned_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%uint",
+                           "%v2uint",
+                           "%v2uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%uint",
+                           "%v3uint",
+                           "%v3uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotKHR",
+                           "%uint",
+                           "%v4uint",
+                           "%v4uint",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSUDotKHR",
+                           "%uchar",  // matches packed component type
+                           "%uint",
+                           "%uint",
+                           "",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSUDotKHR",
+                           "%uint",
+                           "%uint",
+                           "%uint",
+                           "",
+                           true,
+                           ""}));
+
+// UDotAccSat
+INSTANTIATE_TEST_SUITE_P(
+    Valid_UDotAccSat, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpUDotAccSatKHR",
+                           "%uint",
+                           "%v2uint",
+                           "%v2uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpUDotAccSatKHR",
+                           "%uint",
+                           "%v3uint",
+                           "%v3uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpUDotAccSatKHR",
+                           "%uint",
+                           "%v4uint",
+                           "%v4uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpUDotAccSatKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpUDotAccSatKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpUDotAccSatKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uchar",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpUDotAccSatKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpUDotAccSatKHR",
+                           "%uchar",  // matches packed component type
+                           "%uint",
+                           "%uint",
+                           "%uchar",
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpUDotAccSatKHR",
+                           "%uint",
+                           "%uint",
+                           "%uint",
+                           "%uint",
+                           true,
+                           ""}));
+
+// SDotAccSat result signed args signed signed
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SDotAccSat_signed_signed_signed, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%v2int",
+                           "%v2int",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%v3int",
+                           "%v3int",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%v4int",
+                           "%v4int",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4char",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4char",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4char",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4char",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%char",  // matches packed component type
+                           "%int",
+                           "%int",
+                           "%char",  // matches packed component type
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%int",
+                           "%int",
+                           "%int",
+                           true,
+                           ""}));
+
+// SDotAccSat result unsigned args signed unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SDotAccSat_unsigned_signed_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%uint",
+                           "%v2int",
+                           "%v2uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%uint",
+                           "%v3int",
+                           "%v3uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%uint",
+                           "%v4int",
+                           "%v4uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%uchar",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "%uchar",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%uint",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "%uint",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotAccSatKHR",
+                           "%uchar",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "%uchar",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotAccSatKHR",
+                           "%uint",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "%uint",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%uchar",  // matches packed component type
+                           "%int",
+                           "%uint",
+                           "%uchar",  // matches packed component type
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSDotAccSatKHR",
+                           "%uint",
+                           "%int",
+                           "%uint",
+                           "%uint",
+                           true,
+                           ""}));
+
+// SDotAccSat result signed args signed unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SDotAccSat_signed_signed_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%v2int",
+                           "%v2uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%v3int",
+                           "%v3uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%v4int",
+                           "%v4uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSDotAccSatKHR",
+                           "%char",  // matches packed component type
+                           "%int",
+                           "%uint",
+                           "%char",  // matches packed component type
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSDotAccSatKHR",
+                           "%int",
+                           "%int",
+                           "%uint",
+                           "%int",
+                           true,
+                           ""}));
+
+// SUDotAccSat result signed args unsigned unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SUDotAccSat_signed_unsigned_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%v2uint",
+                           "%v2uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%v3uint",
+                           "%v3uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%v4uint",
+                           "%v4uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%char",  // matches packed component type
+                           "%uint",
+                           "%uint",
+                           "%char",  // matches packed component type
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%uint",
+                           "%uint",
+                           "%int",
+                           true,
+                           ""}));
+
+// SUDotAccSat result signed args signed unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SUDotAccSat_signed_signed_unsigned, ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%v2int",
+                           "%v2uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%v3int",
+                           "%v3uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%v4int",
+                           "%v4uint",
+                           "%int",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%char",  // match width
+                           "%v4char",
+                           "%v4uchar",
+                           "%char",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",  // wider width
+                           "%v4char",
+                           "%v4uchar",
+                           "%int",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%char",  // matches packed component type
+                           "%int",
+                           "%uint",
+                           "%char",  // matches packed component type
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%int",
+                           "%int",
+                           "%uint",
+                           "%int",
+                           true,
+                           ""}));
+
+// SUDotAccSat result unsigned args unsigned unsigned
+INSTANTIATE_TEST_SUITE_P(
+    Valid_SUDotAccSat_unsigned_unsigned_unsigned,
+    ValidateSpvKHRIntegerDotProduct,
+    ::testing::Values(Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%uint",
+                           "%v2uint",
+                           "%v2uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%uint",
+                           "%v3uint",
+                           "%v3uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%uint",
+                           "%v4uint",
+                           "%v4uint",
+                           "%uint",
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uchar",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInputAllKHR", "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uint",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%uchar",  // match width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uchar",  // match width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%uint",  // wider width
+                           "%v4uchar",
+                           "%v4uchar",
+                           "%uint",  // wider width
+                           false,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR",
+                            "Int8"},
+                           "OpSUDotAccSatKHR",
+                           "%uchar",  // matches packed component type
+                           "%uint",
+                           "%uint",
+                           "%uchar",  // matches packed component type
+                           true,
+                           ""},
+                      Case{{"DotProductKHR", "DotProductInput4x8BitPackedKHR"},
+                           "OpSUDotAccSatKHR",
+                           "%uint",
+                           "%uint",
+                           "%uint",
+                           "%uint",
+                           true,
+                           ""}));
+
+using ValidateSpvKHRIntegerDotProductSimple = ::testing::Test;
+
+TEST(ValidateSpvKHRIntegerDotProductSimple, DISABLED_RequiresExtension) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple, DISABLED_Invalid_ResultTooNarrow) {
+  // Test across all the instructions.
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_UDot_OperandTypesMatch) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_SDot_OperandTypesMatchExceptSignedness) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_SUDot_OperandTypesMatchExceptSignedness) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_UDotAccSat_OperandTypesMatch) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_SDotAccSat_OperandTypesMatchExceptSignedness) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_SUDotAccSat_OperandTypesMatchExceptSignedness) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_UDot_RequiresUnsigned) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_SUDot_RequiresUnsignedSecondArg) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_UDotAccSat_RequiresUnsigned) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_SUDotAccSat_RequiresUnsignedSecondArg) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_VectorOperandsDisallowPackedFormat) {
+  FAIL();
+}
+
+TEST(ValidateSpvKHRIntegerDotProductSimple,
+     DISABLED_Invalid_ScalarOperandsRequirePackedFormat) {
+  FAIL();
+}
+
+// TODO(dneto): Test valid cases with other scalar integer types
+// TODO(dneto): Test valid cases of length-8 vectors
+// TODO(dneto): Test valid cases of length-16 vectors
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_extension_spv_khr_linkonce_odr.cpp b/test/val/val_extension_spv_khr_linkonce_odr.cpp
new file mode 100644
index 0000000..ac15558
--- /dev/null
+++ b/test/val/val_extension_spv_khr_linkonce_odr.cpp
@@ -0,0 +1,100 @@
+// Copyright (c) 2020 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 OpExtension validator rules.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/enum_string_mapping.h"
+#include "source/extensions.h"
+#include "source/spirv_target_env.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+using ValidateSpvKHRLinkOnceODR = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateSpvKHRLinkOnceODR, Valid) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpExtension "SPV_KHR_linkonce_odr"
+    OpMemoryModel Physical32 OpenCL
+    OpDecorate %var LinkageAttributes "foobar" LinkOnceODR
+
+    %uint = OpTypeInt 32 0
+    %ptr = OpTypePointer CrossWorkgroup %uint
+    %var = OpVariable %ptr CrossWorkgroup
+
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateSpvKHRLinkOnceODR, RequiresExtension) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpCapability Linkage
+    OpMemoryModel Physical32 OpenCL
+    OpDecorate %var LinkageAttributes "foobar" LinkOnceODR
+
+    %uint = OpTypeInt 32 0
+    %ptr = OpTypePointer CrossWorkgroup %uint
+    %var = OpVariable %ptr CrossWorkgroup
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("4th operand of Decorate: operand LinkOnceODR(2) requires one "
+                "of these extensions: SPV_KHR_linkonce_odr \n"
+                "  OpDecorate %1 LinkageAttributes \"foobar\" LinkOnceODR\n"));
+}
+
+TEST_F(ValidateSpvKHRLinkOnceODR, RequiresLinkageCapability) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpExtension "SPV_KHR_linkonce_odr"
+    OpMemoryModel Physical32 OpenCL
+    OpDecorate %var LinkageAttributes "foobar" LinkOnceODR
+
+    %uint = OpTypeInt 32 0
+    %ptr = OpTypePointer CrossWorkgroup %uint
+    %var = OpVariable %ptr CrossWorkgroup
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Operand 2 of Decorate requires one of these capabilities: Linkage \n"
+          "  OpDecorate %1 LinkageAttributes \"foobar\" LinkOnceODR"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp b/test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp
new file mode 100644
index 0000000..f528cb9
--- /dev/null
+++ b/test/val/val_extension_spv_khr_subgroup_uniform_control_flow.cpp
@@ -0,0 +1,110 @@
+// Copyright (c) 2021 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 OpExtension validator rules.
+
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/enum_string_mapping.h"
+#include "source/extensions.h"
+#include "source/spirv_target_env.h"
+#include "test/test_fixture.h"
+#include "test/unit_spirv.h"
+#include "test/val/val_fixtures.h"
+
+namespace spvtools {
+namespace val {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+using ValidateSpvKHRSubgroupUniformControlFlow = spvtest::ValidateBase<bool>;
+
+TEST_F(ValidateSpvKHRSubgroupUniformControlFlow, Valid) {
+  const std::string str = R"(
+    OpCapability Shader
+    OpExtension "SPV_KHR_subgroup_uniform_control_flow"
+    OpMemoryModel Logical Simple
+    OpEntryPoint GLCompute %main "main"
+    OpExecutionMode %main LocalSize 1 1 1
+    OpExecutionMode %main SubgroupUniformControlFlowKHR
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateSpvKHRSubgroupUniformControlFlow, RequiresExtension) {
+  const std::string str = R"(
+    OpCapability Shader
+    OpMemoryModel Logical Simple
+    OpEntryPoint GLCompute %main "main"
+    OpExecutionMode %main LocalSize 1 1 1
+    OpExecutionMode %main SubgroupUniformControlFlowKHR
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("2nd operand of ExecutionMode: operand "
+                        "SubgroupUniformControlFlowKHR(4421) "
+                        "requires one of these extensions: "
+                        "SPV_KHR_subgroup_uniform_control_flow"));
+}
+
+TEST_F(ValidateSpvKHRSubgroupUniformControlFlow, RequiresShaderCapability) {
+  const std::string str = R"(
+    OpCapability Kernel
+    OpCapability Addresses
+    OpExtension "SPV_KHR_subgroup_uniform_control_flow"
+    OpMemoryModel Physical32 OpenCL
+    OpEntryPoint Kernel %main "main"
+    OpExecutionMode %main SubgroupUniformControlFlowKHR
+    
+    %void    = OpTypeVoid
+    %void_fn = OpTypeFunction %void
+
+    %main = OpFunction %void None %void_fn
+    %entry = OpLabel
+    OpReturn
+    OpFunctionEnd
+)";
+  CompileSuccessfully(str.c_str());
+  EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Operand 2 of ExecutionMode requires one of these "
+                        "capabilities: Shader"));
+}
+
+}  // namespace
+}  // namespace val
+}  // namespace spvtools
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index c65d171..ac05749 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -835,7 +835,7 @@
         position_(spv_position_t{0, 0, 0}),
         diagnostic_(spvDiagnosticCreate(&position_, "")) {}
 
-  ~OpTypeArrayLengthTest() { spvDiagnosticDestroy(diagnostic_); }
+  ~OpTypeArrayLengthTest() override { spvDiagnosticDestroy(diagnostic_); }
 
   // Runs spvValidate() on v, printing any errors via spvDiagnosticPrint().
   spv_result_t Val(const SpirvVector& v, const std::string& expected_err = "") {
@@ -1056,6 +1056,8 @@
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_0);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04667"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("OpTypeStruct must not contain an opaque type"));
 }
 
@@ -5522,9 +5524,9 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpDecorate SpecId decoration target <id> "
-                        "'1[%uint_3]' is not a scalar specialization "
-                        "constant."));
+              HasSubstr("SpecId decoration on target <id> "
+                        "'1[%uint_3]' must be a scalar specialization "
+                        "constant"));
 }
 
 TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantOpBad) {
@@ -5544,8 +5546,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is "
-                        "not a scalar specialization constant."));
+              HasSubstr("SpecId decoration on target <id> '1[%1]' "
+                        "must be a scalar specialization constant"));
 }
 
 TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantCompositeBad) {
@@ -5564,8 +5566,8 @@
   CompileSuccessfully(spirv.c_str());
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is "
-                        "not a scalar specialization constant."));
+              HasSubstr("SpecId decoration on target <id> '1[%1]' "
+                        "must be a scalar specialization constant"));
 }
 
 TEST_F(ValidateIdWithMessage, SpecIdTargetGood) {
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index ce4bf1b..11b14fb 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -80,6 +80,7 @@
 %private_image_u32_buffer_0002_r32ui
 %private_image_u32_spd_0002
 %private_image_f32_buffer_0002_r32ui
+%input_flat_u32
 )";
 
   ss << capabilities_and_extensions;
@@ -121,6 +122,8 @@
 OpDecorate %uniform_image_f32_cube_0102_rgba32f Binding 3
 OpDecorate %uniform_sampler DescriptorSet 3
 OpDecorate %uniform_sampler Binding 0
+OpDecorate %input_flat_u32 Flat
+OpDecorate %input_flat_u32 Location 0
 )";
   }
 
@@ -294,6 +297,9 @@
 %ptr_Image_f32 = OpTypePointer Image %f32
 %ptr_image_f32_buffer_0002_r32ui = OpTypePointer Private %type_image_f32_buffer_0002_r32ui
 %private_image_f32_buffer_0002_r32ui = OpVariable %ptr_image_f32_buffer_0002_r32ui Private
+
+%ptr_input_flat_u32 = OpTypePointer Input %u32
+%input_flat_u32 = OpVariable %ptr_input_flat_u32 Input
 )";
 
   if (env == SPV_ENV_UNIVERSAL_1_0) {
@@ -1767,6 +1773,38 @@
           "Expected Image Operand Offset to have 2 components, but given 3"));
 }
 
+TEST_F(ValidateImage, SampleImplicitLodVulkanOffsetWrongSize) {
+  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
+%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Offset %s32vec2_01
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "", "Fragment", "", SPV_ENV_VULKAN_1_0).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Offset-04663"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Image Operand Offset can only be used with "
+                        "OpImage*Gather operations"));
+}
+
+TEST_F(ValidateImage, SampleImplicitLodVulkanOffsetWrongBeforeLegalization) {
+  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
+%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Offset %s32vec2_01
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "", "Fragment", "", SPV_ENV_VULKAN_1_0).c_str());
+  getValidatorOptions()->before_hlsl_legalization = true;
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 TEST_F(ValidateImage, SampleImplicitLodMoreThanOneOffset) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001
@@ -1777,9 +1815,29 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Image Operands Offset, ConstOffset, ConstOffsets, Offsets "
+                "cannot be used together"));
+}
+
+TEST_F(ValidateImage, SampleImplicitLodVulkanMoreThanOneOffset) {
+  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
+%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 ConstOffset|Offset %s32vec2_01 %s32vec2_01
+)";
+
+  CompileSuccessfully(
+      GenerateShaderCode(body, "", "Fragment", "", SPV_ENV_VULKAN_1_0).c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Image Operands Offset, ConstOffset, ConstOffsets "
-                        "cannot be used together"));
+              AnyVUID("VUID-StandaloneSpirv-Offset-04662"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Image Operands Offset, ConstOffset, ConstOffsets, Offsets "
+                "cannot be used together"));
 }
 
 TEST_F(ValidateImage, SampleImplicitLodMinLodWrongType) {
@@ -2980,6 +3038,40 @@
               HasSubstr("Expected Component to be 32-bit int scalar"));
 }
 
+TEST_F(ValidateImage, GatherComponentSuccessVulkan) {
+  const std::string body = R"(
+%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler
+%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_0
+)";
+
+  spv_target_env env = SPV_ENV_VULKAN_1_0;
+  CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", "", env).c_str(),
+                      env);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(env));
+}
+
+TEST_F(ValidateImage, GatherComponentNotConstantVulkan) {
+  const std::string body = R"(
+%input_u32 = OpLoad %u32 %input_flat_u32
+%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101
+%sampler = OpLoad %type_sampler %uniform_sampler
+%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler
+%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %input_u32
+)";
+
+  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(),
+              AnyVUID("VUID-StandaloneSpirv-OpImageGather-04664"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Component Operand to be a const object for "
+                        "Vulkan environment"));
+}
+
 TEST_F(ValidateImage, GatherDimCube) {
   const std::string body = R"(
 %img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101
@@ -3299,6 +3391,8 @@
           .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-Result-04780"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Expected Result Type to have 4 components"));
 }
 
@@ -3893,6 +3987,8 @@
 
   CompileSuccessfully(body.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpImageQuerySizeLod-04659"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr(
@@ -4227,6 +4323,8 @@
 
   CompileSuccessfully(body.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpImageQuerySizeLod-04659"));
   EXPECT_THAT(
       getDiagnosticString(),
       HasSubstr("OpImageQueryLevels must only consume an \"Image\" operand "
@@ -4413,6 +4511,8 @@
   CompileSuccessfully(body.c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-04657"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Sampled image type requires an image type with "
                         "\"Sampled\" operand set to 0 or 1"));
 }
@@ -5733,6 +5833,11 @@
 OpCapability Int64ImageEXT
 OpExtension "SPV_EXT_shader_image_int64"
 )";
+static const std::string capabilities_and_extensions_image64_atomic = R"(
+OpCapability Int64Atomics
+OpCapability Int64ImageEXT
+OpExtension "SPV_EXT_shader_image_int64"
+)";
 static const std::string declarations_image64 = R"(
 %type_image_u64_buffer_0002_r64ui = OpTypeImage %u64 Buffer 0 0 0 2 R64ui
 %ptr_Image_u64 = OpTypePointer Image %u64
@@ -5772,11 +5877,11 @@
 %sum = OpAtomicIAdd %u64 %texel_ptr %u32_1 %u32_0 %u64_1
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body,
-                                         capabilities_and_extensions_image64,
-                                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3,
-                                         "GLSL450", declarations_image64)
-                          .c_str());
+  CompileSuccessfully(
+      GenerateShaderCode(body, capabilities_and_extensions_image64_atomic,
+                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3, "GLSL450",
+                         declarations_image64)
+          .c_str());
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
@@ -5786,11 +5891,11 @@
 %sum = OpAtomicIAdd %u64 %texel_ptr %u32_1 %u32_0 %u64_1
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body,
-                                         capabilities_and_extensions_image64,
-                                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3,
-                                         "GLSL450", declarations_image64)
-                          .c_str());
+  CompileSuccessfully(
+      GenerateShaderCode(body, capabilities_and_extensions_image64_atomic,
+                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3, "GLSL450",
+                         declarations_image64)
+          .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Expected Result Type to be OpTypePointer"));
@@ -5802,11 +5907,11 @@
 %sum = OpAtomicIAdd %u64 %texel_ptr %u32_1 %u32_0 %u64_1
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body,
-                                         capabilities_and_extensions_image64,
-                                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3,
-                                         "GLSL450", declarations_image64)
-                          .c_str());
+  CompileSuccessfully(
+      GenerateShaderCode(body, capabilities_and_extensions_image64_atomic,
+                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3, "GLSL450",
+                         declarations_image64)
+          .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Expected Result Type to be OpTypePointer whose "
@@ -5819,11 +5924,11 @@
 %sum = OpAtomicIAdd %u64 %texel_ptr %u32_1 %u32_0 %u64_1
 )";
 
-  CompileSuccessfully(GenerateShaderCode(body,
-                                         capabilities_and_extensions_image64,
-                                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3,
-                                         "GLSL450", declarations_image64)
-                          .c_str());
+  CompileSuccessfully(
+      GenerateShaderCode(body, capabilities_and_extensions_image64_atomic,
+                         "Fragment", "", SPV_ENV_UNIVERSAL_1_3, "GLSL450",
+                         declarations_image64)
+          .c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Expected Sample for Image with MS 0 to be a valid "
@@ -5958,6 +6063,42 @@
                         "R32f, R32i, or R32ui for Vulkan environment"));
 }
 
+TEST_F(ValidateImage, ImageExecutionModeLimitationNoMode) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 " " %4
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%12 = OpTypeImage %float 2D 0 0 0 1 Rgba8ui
+%13 = OpTypeSampledImage %12
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+%5 = OpVariable %_ptr_UniformConstant_13 UniformConstant
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%4 = OpVariable %_ptr_Input_v4float Input
+%v2float = OpTypeVector %float 2
+%float_1_35631564en19 = OpConstant %float 1.35631564e-19
+%2 = OpFunction %void None %8
+%8224 = OpLabel
+%6 = OpLoad %13 %5
+%19 = OpLoad %v4float %4
+%20 = OpVectorShuffle %v2float %19 %19 0 1
+%21 = OpVectorTimesScalar %v2float %20 %float_1_35631564en19
+%65312 = OpImageSampleImplicitLod %v4float %6 %21
+OpUnreachable
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ImplicitLod instructions require "
+                        "DerivativeGroupQuadsNV or DerivativeGroupLinearNV "
+                        "execution mode for GLCompute execution model"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index 6869e79..a01fc19 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -1267,10 +1267,9 @@
 )";
 
   CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
-  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("Index can only be applied to Fragment output variables"));
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("must be in the Output storage class"));
 }
 
 TEST_F(ValidateInterfacesTest, VulkanLocationsArrayWithComponent) {
@@ -1410,6 +1409,93 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
 }
 
+TEST_F(ValidateInterfacesTest, VulkanLocationArrayWithComponent1) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+OpMemberDecorate %struct 0 Component 0
+OpMemberDecorate %struct 1 Location 0
+OpMemberDecorate %struct 1 Component 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%float_arr = OpTypeArray %float %int_2
+%struct = OpTypeStruct %float_arr %float_arr
+%ptr = OpTypePointer Input %struct
+%in = OpVariable %ptr Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, VulkanLocationArrayWithComponent2) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Location 0
+OpMemberDecorate %struct 0 Component 0
+OpMemberDecorate %struct 1 Location 0
+OpMemberDecorate %struct 1 Component 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%int = OpTypeInt 32 0
+%int_2 = OpConstant %int 2
+%double_arr = OpTypeArray %double %int_2
+%struct = OpTypeStruct %float %double_arr
+%ptr = OpTypePointer Input %struct
+%in = OpVariable %ptr Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateInterfacesTest, DuplicateInterfaceVariableSuccess) {
+  const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in %out %in
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %in Location 0
+OpDecorate %out Location 0
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%in_ptr = OpTypePointer Input %float
+%out_ptr = OpTypePointer Output %float
+%in = OpVariable %in_ptr Input
+%out = OpVariable %out_ptr Output
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index d34c97f..7ebd7c0 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -538,7 +538,6 @@
            OpMemoryModel Logical GLSL450
            OpName %void "void"
            OpModuleProcessed "this is ok in 1.1 and later"
-           OpDecorate %void Volatile ; bogus, but makes the example short
 %void    = OpTypeVoid
 )";
 
@@ -558,7 +557,6 @@
            OpMemoryModel Logical GLSL450
            OpName %void "void"
            OpModuleProcessed "this is ok in 1.1 and later"
-           OpDecorate %void Volatile ; bogus, but makes the example short
 %void    = OpTypeVoid
 )";
 
diff --git a/test/val/val_limits_test.cpp b/test/val/val_limits_test.cpp
index 0ef61e2..8fb80a4 100644
--- a/test/val/val_limits_test.cpp
+++ b/test/val/val_limits_test.cpp
@@ -212,6 +212,7 @@
 %5 = OpFunction %1 None %2
 %7 = OpLabel
 %8 = OpIAdd %3 %4 %4
+     OpSelectionMerge %10 None
      OpSwitch %4 %10)";
 
   // Now add the (literal, label) pairs
@@ -240,6 +241,7 @@
 %5 = OpFunction %1 None %2
 %7 = OpLabel
 %8 = OpIAdd %3 %4 %4
+     OpSelectionMerge %10 None
      OpSwitch %4 %10)";
 
   // Now add the (literal, label) pairs
@@ -271,6 +273,7 @@
 %5 = OpFunction %1 None %2
 %7 = OpLabel
 %8 = OpIAdd %3 %4 %4
+     OpSelectionMerge %10 None
      OpSwitch %4 %10)";
 
   // Now add the (literal, label) pairs
@@ -301,6 +304,7 @@
 %5 = OpFunction %1 None %2
 %7 = OpLabel
 %8 = OpIAdd %3 %4 %4
+     OpSelectionMerge %10 None
      OpSwitch %4 %10)";
 
   // Now add the (literal, label) pairs
diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp
index 8142f85..2a884c4 100644
--- a/test/val/val_memory_test.cpp
+++ b/test/val/val_memory_test.cpp
@@ -22,6 +22,16 @@
 #include "test/val/val_code_generator.h"
 #include "test/val/val_fixtures.h"
 
+// For pretty-printing tuples with spv_target_env.
+std::ostream& operator<<(std::ostream& stream, spv_target_env target)
+{
+  switch (target) {
+    case SPV_ENV_UNIVERSAL_1_3: return stream << "SPV_ENV_UNIVERSAL_1_3";
+    case SPV_ENV_UNIVERSAL_1_4: return stream << "SPV_ENV_UNIVERSAL_1_4";
+    default:                    return stream << (unsigned)target;
+  }
+}
+
 namespace spvtools {
 namespace val {
 namespace {
@@ -51,14 +61,14 @@
 )";
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-04655"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
-                "Variables identified with the UniformConstant storage class "
+      HasSubstr("Variables identified with the UniformConstant storage class "
                 "are used only as handles to refer to opaque resources. Such "
                 "variables must be typed as OpTypeImage, OpTypeSampler, "
-                "OpTypeSampledImage, OpTypeAccelerationStructureNV, "
-                "OpTypeAccelerationStructureKHR, OpTypeRayQueryKHR, "
+                "OpTypeSampledImage, OpTypeAccelerationStructureKHR, "
                 "or an array of one of these types."));
 }
 
@@ -105,14 +115,14 @@
 )";
   CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
   EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-UniformConstant-04655"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("From Vulkan spec, section 14.5.2:\n"
-                "Variables identified with the UniformConstant storage class "
+      HasSubstr("Variables identified with the UniformConstant storage class "
                 "are used only as handles to refer to opaque resources. Such "
                 "variables must be typed as OpTypeImage, OpTypeSampler, "
-                "OpTypeSampledImage, OpTypeAccelerationStructureNV, "
-                "OpTypeAccelerationStructureKHR, OpTypeRayQueryKHR, "
+                "OpTypeSampledImage, OpTypeAccelerationStructureKHR, "
                 "or an array of one of these types."));
 }
 
@@ -451,7 +461,8 @@
       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 "
+                "following storage classes: Output, Private, Function or "
+                "Workgroup\n  %5 "
                 "= OpVariable %_ptr_Input_float Input %float_1\n"));
 }
 
@@ -735,7 +746,7 @@
       %float = OpTypeFloat 32
      %uint = OpTypeInt 32 0
 %_runtimearr_float = OpTypeRuntimeArray %float
-  %_struct_7 = OpTypeStruct %float %_runtimearr_float
+  %_struct_7 = OpTypeStruct %float
 %_ptr_Function__struct_7  = OpTypePointer Function %_struct_7
           %1 = OpFunction %void None %3
           %9 = OpLabel
@@ -2333,11 +2344,12 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
-OpDecorate %array_t Block
+OpDecorate %struct Block
 %uint_t = OpTypeInt 32 0
 %inner_array_t = OpTypeRuntimeArray %uint_t
 %array_t = OpTypeRuntimeArray %inner_array_t
-%array_ptr = OpTypePointer StorageBuffer %array_t
+%struct = OpTypeStruct %array_t
+%array_ptr = OpTypePointer StorageBuffer %struct
 %2 = OpVariable %array_ptr StorageBuffer
 %void = OpTypeVoid
 %func_t = OpTypeFunction %void
@@ -2493,13 +2505,14 @@
 OpMemoryModel Logical GLSL450
 OpEntryPoint Fragment %func "func"
 OpExecutionMode %func OriginUpperLeft
-OpDecorate %array_t Block
+OpDecorate %struct Block
 %uint_t = OpTypeInt 32 0
 %dim = OpConstant %uint_t 1
 %sampler_t = OpTypeSampler
 %inner_array_t = OpTypeRuntimeArray %uint_t
 %array_t = OpTypeRuntimeArray %inner_array_t
-%array_ptr = OpTypePointer StorageBuffer %array_t
+%struct = OpTypeStruct %array_t
+%array_ptr = OpTypePointer StorageBuffer %struct
 %2 = OpVariable %array_ptr StorageBuffer
 %void = OpTypeVoid
 %func_t = OpTypeFunction %void
@@ -3443,9 +3456,10 @@
 }
 
 using ValidateSizedVariable =
-    spvtest::ValidateBase<std::tuple<std::string, std::string, std::string>>;
+    spvtest::ValidateBase<std::tuple<std::string, std::string,
+                                     std::string, spv_target_env>>;
 
-CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit) {
+CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit, bool buffer_block) {
   CodeGenerator generator;
   generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n";
   generator.extensions_ =
@@ -3453,20 +3467,25 @@
       "\"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.before_types_ = "OpMemberDecorate %char_buffer_block 0 Offset 0\n";
+    if (buffer_block)
+      generator.before_types_ += "OpDecorate %char_buffer_block BufferBlock\n";
+
     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.before_types_ =
+        "OpMemberDecorate %half_buffer_block 0 Offset 0\n"
+        "OpMemberDecorate %short_buffer_block 0 Offset 0\n";
+    if (buffer_block) {
+      generator.before_types_ +=
+          "OpDecorate %half_buffer_block BufferBlock\n"
+          "OpDecorate %short_buffer_block BufferBlock\n";
+    }
+
     generator.types_ = R"(%void = OpTypeVoid
 %short = OpTypeInt 16 0
 %half = OpTypeFloat 16
@@ -3489,6 +3508,10 @@
   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());
+  const spv_target_env target = std::get<3>(GetParam());
+
+  ASSERT_TRUE(target == SPV_ENV_UNIVERSAL_1_3 ||
+              target == SPV_ENV_UNIVERSAL_1_4);
 
   bool type_8bit = false;
   if (var_type == "%char" || var_type == "%char4" ||
@@ -3496,7 +3519,16 @@
     type_8bit = true;
   }
 
-  auto generator = GetSizedVariableCodeGenerator(type_8bit);
+  const bool buffer_block = var_type.find("buffer_block") != std::string::npos;
+
+  auto generator = GetSizedVariableCodeGenerator(type_8bit, buffer_block);
+
+  if (capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR" ||
+      capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR") {
+    generator.extensions_ +=
+        "OpExtension \"SPV_KHR_workgroup_memory_explicit_layout\"\n";
+  }
+
   generator.types_ += "%ptr_type = OpTypePointer " + storage_class + " " +
                       var_type + "\n%var = OpVariable %ptr_type " +
                       storage_class + "\n";
@@ -3526,7 +3558,6 @@
     }
     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);
@@ -3536,11 +3567,30 @@
           (capability == "StorageBuffer16BitAccess" && buffer_block);
     }
     storage_class_ok = true;
+  } else if (storage_class == "Workgroup") {
+    if (type_8bit) {
+      capability_ok =
+          capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR";
+    } else {
+      capability_ok =
+          capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR";
+    }
+    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) {
+  CompileSuccessfully(generator.Build(), target);
+  spv_result_t result = ValidateInstructions(target);
+  if (target < SPV_ENV_UNIVERSAL_1_4 &&
+      (capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR" ||
+       capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR")) {
+    EXPECT_EQ(SPV_ERROR_WRONG_VERSION, result);
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("requires SPIR-V version 1.4 or later"));
+  } else if (buffer_block && target > SPV_ENV_UNIVERSAL_1_3) {
+    EXPECT_EQ(SPV_ERROR_WRONG_VERSION, result);
+    EXPECT_THAT(getDiagnosticString(),
+                HasSubstr("requires SPIR-V version 1.3 or earlier"));
+  } else if (capability_ok) {
     EXPECT_EQ(SPV_SUCCESS, result);
   } else {
     EXPECT_EQ(SPV_ERROR_INVALID_ID, result);
@@ -3565,8 +3615,10 @@
     Combine(Values("UniformConstant", "Input", "Output", "Workgroup",
                    "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"),
             Values("StorageBuffer8BitAccess",
-                   "UniformAndStorageBuffer8BitAccess", "StoragePushConstant8"),
-            Values("%char", "%char4", "%char_buffer_block")));
+                   "UniformAndStorageBuffer8BitAccess", "StoragePushConstant8",
+                   "WorkgroupMemoryExplicitLayout8BitAccessKHR"),
+            Values("%char", "%char4", "%char_buffer_block"),
+            Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4)));
 
 INSTANTIATE_TEST_SUITE_P(
     Storage16, ValidateSizedVariable,
@@ -3574,9 +3626,11 @@
                    "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"),
             Values("StorageBuffer16BitAccess",
                    "UniformAndStorageBuffer16BitAccess",
-                   "StoragePushConstant16", "StorageInputOutput16"),
+                   "StoragePushConstant16", "StorageInputOutput16",
+                   "WorkgroupMemoryExplicitLayout16BitAccessKHR"),
             Values("%short", "%half", "%short4", "%half4", "%mat4x4",
-                   "%short_buffer_block", "%half_buffer_block")));
+                   "%short_buffer_block", "%half_buffer_block"),
+            Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4)));
 
 using ValidateSizedLoadStore =
     spvtest::ValidateBase<std::tuple<std::string, uint32_t, std::string>>;
@@ -3990,6 +4044,109 @@
                 "typed as OpTypeStruct, or an array of this type"));
 }
 
+TEST_F(ValidateMemory, VulkanInvariantOutputSuccess) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpDecorate %var Location 0
+OpDecorate %var Invariant
+%void = OpTypeVoid
+%f32 = OpTypeFloat 32
+%ptr_output = OpTypePointer Output %f32
+%var = OpVariable %ptr_output Output
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateMemory, VulkanInvariantInputStructSuccess) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %var Location 0
+OpMemberDecorate %struct 1 Invariant
+%void = OpTypeVoid
+%f32 = OpTypeFloat 32
+%struct = OpTypeStruct %f32 %f32
+%ptr_input = OpTypePointer Input %struct
+%var = OpVariable %ptr_input Input
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateMemory, VulkanInvariantWrongStorageClass) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpDecorate %var Invariant
+%void = OpTypeVoid
+%f32 = OpTypeFloat 32
+%ptr_private = OpTypePointer Private %f32
+%var = OpVariable %ptr_private Private
+%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(),
+              AnyVUID("VUID-StandaloneSpirv-Invariant-04677"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(
+          "Variable decorated with Invariant must only be identified with the "
+          "Input or Output storage class in Vulkan environment."));
+}
+
+TEST_F(ValidateMemory, VulkanInvariantMemberWrongStorageClass) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpMemberDecorate %struct 1 Invariant
+%void = OpTypeVoid
+%f32 = OpTypeFloat 32
+%struct = OpTypeStruct %f32 %f32
+%ptr_private = OpTypePointer Private %struct
+%var = OpVariable %ptr_private Private
+%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(),
+              AnyVUID("VUID-StandaloneSpirv-Invariant-04677"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Variable struct member decorated with Invariant must "
+                        "only be identified with the Input or Output storage "
+                        "class in Vulkan environment."));
+}
+
 TEST_F(ValidateMemory, PhysicalStorageBufferPtrEqual) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -4080,6 +4237,166 @@
           "Cannot use a pointer in the PhysicalStorageBuffer storage class"));
 }
 
+TEST_F(ValidateMemory, VulkanInitializerWithWorkgroupStorageClassBad) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%float = OpTypeFloat 32
+%float_ptr = OpTypePointer Workgroup %float
+%init_val = OpConstant %float 1.0
+%1 = OpVariable %float_ptr Workgroup %init_val
+%void = OpTypeVoid
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%2 = 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("Variable initializers in Workgroup storage class are "
+                        "limited to OpConstantNull"));
+}
+
+TEST_F(ValidateMemory, VulkanInitializerWithWorkgroupStorageClassGood) {
+  std::string spirv = R"(
+OpCapability Shader
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%float = OpTypeFloat 32
+%float_ptr = OpTypePointer Workgroup %float
+%init_val = OpConstantNull %float
+%1 = OpVariable %float_ptr Workgroup %init_val
+%void = OpTypeVoid
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_0);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+}
+
+TEST_F(ValidateMemory, LoadRuntimeArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%rta = OpTypeRuntimeArray %int
+%block = OpTypeStruct %rta
+%ptr_rta = OpTypePointer StorageBuffer %rta
+%ptr_block = OpTypePointer StorageBuffer %block
+%var = OpVariable %ptr_block StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%gep = OpAccessChain %ptr_rta %var %int_0
+%ld = OpLoad %rta %gep
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot load a runtime-sized array"));
+}
+
+TEST_F(ValidateMemory, LoadRuntimeArrayInStruct) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%rta = OpTypeRuntimeArray %int
+%block = OpTypeStruct %rta
+%ptr_rta = OpTypePointer StorageBuffer %rta
+%ptr_block = OpTypePointer StorageBuffer %block
+%var = OpVariable %ptr_block StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpLoad %block %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot load a runtime-sized array"));
+}
+
+TEST_F(ValidateMemory, LoadRuntimeArrayInArray) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%rta = OpTypeRuntimeArray %int
+%block = OpTypeStruct %rta
+%array = OpTypeArray %block %int_4
+%ptr_rta = OpTypePointer StorageBuffer %rta
+%ptr_block = OpTypePointer StorageBuffer %block
+%ptr_array = OpTypePointer StorageBuffer %array
+%var = OpVariable %ptr_array StorageBuffer
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+%ld = OpLoad %array %var
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Cannot load a runtime-sized array"));
+}
+
+TEST_F(ValidateMemory, Pre1p4WorkgroupMemoryBadLayoutOk) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpDecorate %struct Block
+OpMemberDecorate %struct 0 Offset 0
+%void = OpTypeVoid
+%bool = OpTypeBool
+%struct = OpTypeStruct %bool
+%ptr = OpTypePointer Workgroup %struct
+%var = OpVariable %ptr Workgroup
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_misc_test.cpp b/test/val/val_misc_test.cpp
index 499b5b2..b0e46bf 100644
--- a/test/val/val_misc_test.cpp
+++ b/test/val/val_misc_test.cpp
@@ -273,6 +273,30 @@
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Cannot create undefined values with void type"));
 }
+
+TEST_F(ValidateMisc, VulkanInvalidStorageClass) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %func "shader"
+%int = OpTypeInt 32 0
+%ptr = OpTypePointer CrossWorkgroup %int
+%var = OpVariable %ptr CrossWorkgroup
+%void   = OpTypeVoid
+%void_f = OpTypeFunction %void
+%func   = OpFunction %void None %void_f
+%label  = OpLabel
+          OpReturn
+          OpFunctionEnd
+)";
+
+  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04643"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Invalid storage class for target environment"));
+}
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index 7b23159..02a6132 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -62,11 +62,14 @@
   spv_target_env env = SPV_ENV_VULKAN_1_0;
   CompileSuccessfully(spirv, env);
   EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-LocalSize-06426"));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("In the Vulkan environment, GLCompute execution model entry "
-                "points require either the LocalSize execution mode or an "
-                "object decorated with WorkgroupSize must be specified."));
+      HasSubstr(
+          "In the Vulkan environment, GLCompute execution model entry "
+          "points require either the LocalSize or LocalSizeId execution mode "
+          "or an object decorated with WorkgroupSize must be specified."));
 }
 
 TEST_F(ValidateMode, GLComputeNoModeVulkanWorkgroupSize) {
@@ -99,6 +102,40 @@
   EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
 }
 
+TEST_F(ValidateMode, GLComputeVulkanLocalSizeIdBad) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %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_VULKAN_1_1;  // need SPIR-V 1.2
+  CompileSuccessfully(spirv, env);
+  EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("LocalSizeId mode is not allowed by the current environment."));
+}
+
+TEST_F(ValidateMode, GLComputeVulkanLocalSizeIdGood) {
+  const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %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_VULKAN_1_1;  // need SPIR-V 1.2
+  CompileSuccessfully(spirv, env);
+  spvValidatorOptionsSetAllowLocalSizeId(getValidatorOptions(), true);
+  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
+}
+
 TEST_F(ValidateMode, FragmentOriginLowerLeftVulkan) {
   const std::string spirv = R"(
 OpCapability Shader
@@ -670,7 +707,7 @@
                                  Values("Kernel"),
                                  Values("LocalSizeHint 1 1 1", "VecTypeHint 4",
                                         "ContractionOff",
-                                        "LocalSizeHintId %int1"),
+                                        "LocalSizeHintId %int1 %int1 %int1"),
                                  Values(SPV_ENV_UNIVERSAL_1_3)));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -682,7 +719,7 @@
         Values("Geometry", "TessellationControl", "TessellationEvaluation",
                "GLCompute", "Vertex", "Fragment"),
         Values("LocalSizeHint 1 1 1", "VecTypeHint 4", "ContractionOff",
-               "LocalSizeHintId %int1"),
+               "LocalSizeHintId %int1 %int1 %int1"),
         Values(SPV_ENV_UNIVERSAL_1_3)));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -861,7 +898,7 @@
 OpCapability Shader
 OpMemoryModel Logical GLSL450
 OpEntryPoint Kernel %main "main"
-OpExecutionMode %main LocalSizeHintId %int_1
+OpExecutionMode %main LocalSizeHintId %int_1 %int_1 %int_1
 %int = OpTypeInt 32 0
 %int_1 = OpConstant %int 1
 )" + kVoidFunction;
@@ -880,7 +917,7 @@
 OpCapability Shader
 OpMemoryModel Logical GLSL450
 OpEntryPoint Kernel %main "main"
-OpExecutionModeId %main LocalSizeHintId %int_1
+OpExecutionModeId %main LocalSizeHintId %int_1 %int_1 %int_1
 %int = OpTypeInt 32 0
 %int_1 = OpConstant %int 1
 )" + kVoidFunction;
@@ -896,7 +933,7 @@
 OpCapability Shader
 OpMemoryModel Logical GLSL450
 OpEntryPoint Vertex %main "main"
-OpExecutionModeId %main LocalSizeHintId %int_1
+OpExecutionModeId %main LocalSizeHintId %int_1 %int_1 %int_1
 %int = OpTypeInt 32 0
 %int_ptr = OpTypePointer Private %int
 %int_1 = OpVariable %int_ptr Private
diff --git a/test/val/val_state_test.cpp b/test/val/val_state_test.cpp
index b2d2604..65cb1c3 100644
--- a/test/val/val_state_test.cpp
+++ b/test/val/val_state_test.cpp
@@ -43,7 +43,7 @@
         options_(spvValidatorOptionsCreate()),
         state_(context_, options_, kFakeBinary, 0, 1) {}
 
-  ~ValidationStateTest() {
+  ~ValidationStateTest() override {
     spvContextDestroy(context_);
     spvValidatorOptionsDestroy(options_);
   }
diff --git a/test/val/val_storage_test.cpp b/test/val/val_storage_test.cpp
index e6f98bf..35f6a8d 100644
--- a/test/val/val_storage_test.cpp
+++ b/test/val/val_storage_test.cpp
@@ -30,6 +30,7 @@
 using ValidateStorage = spvtest::ValidateBase<std::string>;
 using ValidateStorageClass =
     spvtest::ValidateBase<std::tuple<std::string, bool, bool, std::string>>;
+using ValidateStorageExecutionModel = spvtest::ValidateBase<std::string>;
 
 TEST_F(ValidateStorage, FunctionStorageInsideFunction) {
   char str[] = R"(
@@ -250,6 +251,46 @@
               HasSubstr("OpFunctionCall Argument <id> '"));
 }
 
+TEST_P(ValidateStorageExecutionModel, VulkanOutsideStoreFailure) {
+  std::stringstream ss;
+  ss << R"(
+              OpCapability Shader
+              OpCapability RayTracingKHR
+              OpExtension "SPV_KHR_ray_tracing"
+              OpMemoryModel Logical GLSL450
+              OpEntryPoint )"
+     << GetParam() << R"(  %func "func" %output
+              OpDecorate %output Location 0
+%intt       = OpTypeInt 32 0
+%int0       = OpConstant %intt 0
+%voidt      = OpTypeVoid
+%vfunct     = OpTypeFunction %voidt
+%outputptrt = OpTypePointer Output %intt
+%output     = OpVariable %outputptrt Output
+%func       = OpFunction %voidt None %vfunct
+%funcl      = OpLabel
+              OpStore %output %int0
+              OpReturn
+              OpFunctionEnd
+)";
+
+  CompileSuccessfully(ss.str(), SPV_ENV_VULKAN_1_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04644"));
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("in Vulkan evironment, Output Storage Class must not be used "
+                "in GLCompute, RayGenerationKHR, IntersectionKHR, AnyHitKHR, "
+                "ClosestHitKHR, MissKHR, or CallableKHR execution models"));
+}
+
+INSTANTIATE_TEST_SUITE_P(MatrixExecutionModel, ValidateStorageExecutionModel,
+                         ::testing::Values("RayGenerationKHR",
+                                           "IntersectionKHR", "AnyHitKHR",
+                                           "ClosestHitKHR", "MissKHR",
+                                           "CallableKHR"));
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_validation_state_test.cpp b/test/val/val_validation_state_test.cpp
index 7a38d3a..3dd9e64 100644
--- a/test/val/val_validation_state_test.cpp
+++ b/test/val/val_validation_state_test.cpp
@@ -257,6 +257,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04634"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Entry points may not have a call graph with cycles.\n "
                         " %1 = OpFunction %void Pure|Const %3\n"));
 }
@@ -274,6 +276,8 @@
   EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
             ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_1));
   EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-StandaloneSpirv-None-04634"));
+  EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Entry points may not have a call graph with cycles.\n "
                         " %1 = OpFunction %void Pure|Const %3\n"));
 }
diff --git a/test/wasm/test.js b/test/wasm/test.js
new file mode 100644
index 0000000..7f0d8f3
--- /dev/null
+++ b/test/wasm/test.js
@@ -0,0 +1,64 @@
+// Copyright (c) 2020 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.
+
+const spirvTools = require("../../out/web/spirv-tools");
+const fs = require("fs");
+const util = require("util");
+const readFile = util.promisify(fs.readFile);
+
+const SPV_PATH = "./test/fuzzers/corpora/spv/simple.spv";
+
+const test = async () => {
+  const spv = await spirvTools();
+
+  // disassemble from file
+  const buffer = await readFile(SPV_PATH);
+  const disFileResult = spv.dis(
+    buffer,
+    spv.SPV_ENV_UNIVERSAL_1_3,
+    spv.SPV_BINARY_TO_TEXT_OPTION_INDENT |
+      spv.SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
+      spv.SPV_BINARY_TO_TEXT_OPTION_COLOR
+  );
+  console.log("dis from file:\n", disFileResult);
+
+  // assemble
+  const source = `
+             OpCapability Linkage 
+             OpCapability Shader 
+             OpMemoryModel Logical GLSL450 
+             OpSource GLSL 450 
+             OpDecorate %spec SpecId 1 
+      %int = OpTypeInt 32 1 
+     %spec = OpSpecConstant %int 0 
+    %const = OpConstant %int 42`;
+  const asResult = spv.as(
+    source,
+    spv.SPV_ENV_UNIVERSAL_1_3,
+    spv.SPV_TEXT_TO_BINARY_OPTION_NONE
+  );
+  console.log(`as returned ${asResult.byteLength} bytes`);
+
+  // re-disassemble
+  const disResult = spv.dis(
+    asResult,
+    spv.SPV_ENV_UNIVERSAL_1_3,
+    spv.SPV_BINARY_TO_TEXT_OPTION_INDENT |
+      spv.SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
+      spv.SPV_BINARY_TO_TEXT_OPTION_COLOR
+  );
+  console.log("dis:\n", disResult);
+};
+
+test();
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 124a332..6039089 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -48,6 +48,7 @@
     add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS_FULL_VISIBILITY})
   endif()
   add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_tool(TARGET spirv-lint SRCS lint/lint.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-lint SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_tool(TARGET spirv-cfg
                     SRCS cfg/cfg.cpp
                          cfg/bin_to_dot.h
@@ -56,7 +57,7 @@
   target_include_directories(spirv-cfg 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)
+                            spirv-cfg spirv-link spirv-lint)
   if(NOT DEFINED IOS_PLATFORM)
     set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-reduce)
   endif()
diff --git a/tools/as/as.cpp b/tools/as/as.cpp
index f6e9629..c8a4445 100644
--- a/tools/as/as.cpp
+++ b/tools/as/as.cpp
@@ -129,7 +129,7 @@
   }
 
   std::vector<char> contents;
-  if (!ReadFile<char>(inFile, "r", &contents)) return 1;
+  if (!ReadTextFile<char>(inFile, &contents)) return 1;
 
   spv_binary binary;
   spv_diagnostic diagnostic = nullptr;
diff --git a/tools/cfg/cfg.cpp b/tools/cfg/cfg.cpp
index ce7f1c2..6a5faa1 100644
--- a/tools/cfg/cfg.cpp
+++ b/tools/cfg/cfg.cpp
@@ -69,16 +69,16 @@
           if (0 == strcmp(argv[argi], "--help")) {
             print_usage(argv[0]);
             return 0;
-          } else if (0 == strcmp(argv[argi], "--version")) {
+          }
+          if (0 == strcmp(argv[argi], "--version")) {
             printf("%s EXPERIMENTAL\n", spvSoftwareVersionDetailsString());
             printf("Target: %s\n",
                    spvTargetEnvDescription(kDefaultEnvironment));
             return 0;
-          } else {
-            print_usage(argv[0]);
-            return 1;
           }
-        } break;
+          print_usage(argv[0]);
+          return 1;
+        }
         case 0: {
           // Setting a filename of "-" to indicate stdin.
           if (!inFile) {
@@ -104,7 +104,7 @@
 
   // Read the input binary.
   std::vector<uint32_t> contents;
-  if (!ReadFile<uint32_t>(inFile, "rb", &contents)) return 1;
+  if (!ReadBinaryFile<uint32_t>(inFile, &contents)) return 1;
   spv_context context = spvContextCreate(kDefaultEnvironment);
   spv_diagnostic diagnostic = nullptr;
 
diff --git a/tools/dis/dis.cpp b/tools/dis/dis.cpp
index bdeeef1..64380db 100644
--- a/tools/dis/dis.cpp
+++ b/tools/dis/dis.cpp
@@ -180,7 +180,7 @@
 
   // Read the input binary.
   std::vector<uint32_t> contents;
-  if (!ReadFile<uint32_t>(inFile, "rb", &contents)) return 1;
+  if (!ReadBinaryFile<uint32_t>(inFile, &contents)) return 1;
 
   // If printing to standard output, then spvBinaryToText should
   // do the printing.  In particular, colour printing on Windows is
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 5ee0fb1..306f925 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -39,6 +39,8 @@
 
 namespace {
 
+enum class FuzzingTarget { kSpirv, kWgsl };
+
 // Check that the std::system function can actually be used.
 bool CheckExecuteCommand() {
   int res = std::system(nullptr);
@@ -141,6 +143,12 @@
                  that was used previously.
                - simple: each time a fuzzer pass is requested, one is provided
                  at random from the set of enabled passes.
+  --fuzzing-target=
+              This option will adjust probabilities of applying certain
+              transformations s.t. the module always remains valid according
+              to the semantics of some fuzzing target. Available targets:
+              - spir-v - module is valid according to the SPIR-V spec.
+              - wgsl - module is valid according to the WGSL spec.
   --replay
                File from which to read a sequence of transformations to replay
                (instead of fuzzing)
@@ -204,15 +212,15 @@
     std::vector<std::string>* interestingness_test,
     std::string* shrink_transformations_file,
     std::string* shrink_temp_file_prefix,
-    spvtools::fuzz::Fuzzer::RepeatedPassStrategy* repeated_pass_strategy,
-    spvtools::FuzzerOptions* fuzzer_options,
+    spvtools::fuzz::RepeatedPassStrategy* repeated_pass_strategy,
+    FuzzingTarget* fuzzing_target, spvtools::FuzzerOptions* fuzzer_options,
     spvtools::ValidatorOptions* validator_options) {
   uint32_t positional_arg_index = 0;
   bool only_positional_arguments_remain = false;
   bool force_render_red = false;
 
   *repeated_pass_strategy =
-      spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations;
+      spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations;
 
   for (int argi = 1; argi < argc; ++argi) {
     const char* cur_arg = argv[argi];
@@ -250,14 +258,14 @@
                               sizeof("--repeated-pass-strategy=") - 1)) {
         std::string strategy = spvtools::utils::SplitFlagArgs(cur_arg).second;
         if (strategy == "looped") {
-          *repeated_pass_strategy = spvtools::fuzz::Fuzzer::
-              RepeatedPassStrategy::kLoopedWithRecommendations;
+          *repeated_pass_strategy =
+              spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations;
         } else if (strategy == "random") {
-          *repeated_pass_strategy = spvtools::fuzz::Fuzzer::
-              RepeatedPassStrategy::kRandomWithRecommendations;
+          *repeated_pass_strategy =
+              spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations;
         } else if (strategy == "simple") {
           *repeated_pass_strategy =
-              spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kSimple;
+              spvtools::fuzz::RepeatedPassStrategy::kSimple;
         } else {
           std::stringstream ss;
           ss << "Unknown repeated pass strategy '" << strategy << "'"
@@ -266,6 +274,20 @@
           spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str());
           return {FuzzActions::STOP, 1};
         }
+      } else if (0 == strncmp(cur_arg, "--fuzzing-target=",
+                              sizeof("--fuzzing-target=") - 1)) {
+        std::string target = spvtools::utils::SplitFlagArgs(cur_arg).second;
+        if (target == "spir-v") {
+          *fuzzing_target = FuzzingTarget::kSpirv;
+        } else if (target == "wgsl") {
+          *fuzzing_target = FuzzingTarget::kWgsl;
+        } else {
+          std::stringstream ss;
+          ss << "Unknown fuzzing target '" << target << "'" << std::endl;
+          ss << "Valid options are 'spir-v' and 'wgsl'.";
+          spvtools::Error(FuzzDiagnostic, nullptr, {}, ss.str().c_str());
+          return {FuzzActions::STOP, 1};
+        }
       } else if (0 == strncmp(cur_arg, "--replay-range=",
                               sizeof("--replay-range=") - 1)) {
         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
@@ -549,8 +571,8 @@
           const std::vector<uint32_t>& binary_in,
           const spvtools::fuzz::protobufs::FactSequence& initial_facts,
           const std::string& donors,
-          spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy,
-          std::vector<uint32_t>* binary_out,
+          spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
+          FuzzingTarget fuzzing_target, std::vector<uint32_t>* binary_out,
           spvtools::fuzz::protobufs::TransformationSequence*
               transformations_applied) {
   auto message_consumer = spvtools::utils::CLIMessageConsumer;
@@ -568,8 +590,8 @@
         [donor_filename, message_consumer,
          target_env]() -> std::unique_ptr<spvtools::opt::IRContext> {
           std::vector<uint32_t> donor_binary;
-          if (!ReadFile<uint32_t>(donor_filename.c_str(), "rb",
-                                  &donor_binary)) {
+          if (!ReadBinaryFile<uint32_t>(donor_filename.c_str(),
+                                        &donor_binary)) {
             return nullptr;
           }
           return spvtools::BuildModule(target_env, message_consumer,
@@ -578,24 +600,46 @@
         });
   }
 
-  auto fuzz_result =
-      spvtools::fuzz::Fuzzer(
-          target_env, message_consumer, binary_in, initial_facts,
-          donor_suppliers,
-          spvtools::MakeUnique<spvtools::fuzz::PseudoRandomGenerator>(
-              fuzzer_options->has_random_seed
-                  ? fuzzer_options->random_seed
-                  : static_cast<uint32_t>(std::random_device()())),
-          fuzzer_options->all_passes_enabled, repeated_pass_strategy,
-          fuzzer_options->fuzzer_pass_validation_enabled, validator_options)
-          .Run();
-  *binary_out = std::move(fuzz_result.transformed_binary);
-  *transformations_applied = std::move(fuzz_result.applied_transformations);
-  if (fuzz_result.status !=
-      spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
+  std::unique_ptr<spvtools::opt::IRContext> ir_context;
+  if (!spvtools::fuzz::fuzzerutil::BuildIRContext(target_env, message_consumer,
+                                                  binary_in, validator_options,
+                                                  &ir_context)) {
+    spvtools::Error(FuzzDiagnostic, nullptr, {}, "Initial binary is invalid");
+    return false;
+  }
+
+  assert((fuzzing_target == FuzzingTarget::kWgsl ||
+          fuzzing_target == FuzzingTarget::kSpirv) &&
+         "Not all fuzzing targets are handled");
+  auto fuzzer_context = spvtools::MakeUnique<spvtools::fuzz::FuzzerContext>(
+      spvtools::MakeUnique<spvtools::fuzz::PseudoRandomGenerator>(
+          fuzzer_options->has_random_seed
+              ? fuzzer_options->random_seed
+              : static_cast<uint32_t>(std::random_device()())),
+      spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()),
+      fuzzing_target == FuzzingTarget::kWgsl);
+
+  auto transformation_context =
+      spvtools::MakeUnique<spvtools::fuzz::TransformationContext>(
+          spvtools::MakeUnique<spvtools::fuzz::FactManager>(ir_context.get()),
+          validator_options);
+  transformation_context->GetFactManager()->AddInitialFacts(message_consumer,
+                                                            initial_facts);
+
+  spvtools::fuzz::Fuzzer fuzzer(
+      std::move(ir_context), std::move(transformation_context),
+      std::move(fuzzer_context), message_consumer, donor_suppliers,
+      fuzzer_options->all_passes_enabled, repeated_pass_strategy,
+      fuzzer_options->fuzzer_pass_validation_enabled, validator_options, false);
+  auto fuzz_result = fuzzer.Run(0);
+  if (fuzz_result.status ==
+      spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule) {
     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
     return false;
   }
+
+  fuzzer.GetIRContext()->module()->ToBinary(binary_out, true);
+  *transformations_applied = fuzzer.GetTransformationSequence();
   return true;
 }
 
@@ -629,6 +673,19 @@
   transformations_file.close();
 }
 
+// The Chromium project applies the following patch to the protobuf library:
+//
+// source.chromium.org/chromium/chromium/src/+/main:third_party/protobuf/patches/0003-remove-static-initializers.patch
+//
+// This affects how Status objects must be constructed. This method provides a
+// convenient way to get the OK status that works both with and without the
+// patch. With the patch OK is a StatusPod, from which a Status can be
+// constructed. Without the patch, OK is already a Status, and we harmlessly
+// copy-construct the result from it.
+google::protobuf::util::Status GetProtobufOkStatus() {
+  return google::protobuf::util::Status(google::protobuf::util::Status::OK);
+}
+
 // Dumps |transformations| to file |filename| in JSON format. Useful for
 // interactive debugging.
 void DumpTransformationsJson(
@@ -639,7 +696,7 @@
   json_options.add_whitespace = true;
   auto json_generation_status = google::protobuf::util::MessageToJsonString(
       transformations, &json_string, json_options);
-  if (json_generation_status == google::protobuf::util::Status::OK) {
+  if (json_generation_status == GetProtobufOkStatus()) {
     std::ofstream transformations_json_file(filename);
     transformations_json_file << json_string;
     transformations_json_file.close();
@@ -656,7 +713,8 @@
   std::vector<std::string> interestingness_test;
   std::string shrink_transformations_file;
   std::string shrink_temp_file_prefix = "temp_";
-  spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy;
+  spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy;
+  auto fuzzing_target = FuzzingTarget::kSpirv;
 
   spvtools::FuzzerOptions fuzzer_options;
   spvtools::ValidatorOptions validator_options;
@@ -665,14 +723,15 @@
       ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file,
                  &replay_transformations_file, &interestingness_test,
                  &shrink_transformations_file, &shrink_temp_file_prefix,
-                 &repeated_pass_strategy, &fuzzer_options, &validator_options);
+                 &repeated_pass_strategy, &fuzzing_target, &fuzzer_options,
+                 &validator_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)) {
+  if (!ReadBinaryFile<uint32_t>(in_binary_file.c_str(), &binary_in)) {
     return 1;
   }
 
@@ -688,9 +747,8 @@
     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)) {
+    if (GetProtobufOkStatus() != google::protobuf::util::JsonStringToMessage(
+                                     facts_json_string, &initial_facts)) {
       spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
       return 1;
     }
@@ -703,16 +761,16 @@
 
   switch (status.action) {
     case FuzzActions::FORCE_RENDER_RED:
-      if (!spvtools::fuzz::ForceRenderRed(target_env, validator_options,
-                                          binary_in, initial_facts,
-                                          &binary_out)) {
+      if (!spvtools::fuzz::ForceRenderRed(
+              target_env, validator_options, binary_in, initial_facts,
+              spvtools::utils::CLIMessageConsumer, &binary_out)) {
         return 1;
       }
       break;
     case FuzzActions::FUZZ:
       if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in,
-                initial_facts, donors_file, repeated_pass_strategy, &binary_out,
-                &transformations_applied)) {
+                initial_facts, donors_file, repeated_pass_strategy,
+                fuzzing_target, &binary_out, &transformations_applied)) {
         return 1;
       }
       break;
@@ -770,7 +828,7 @@
     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) {
+    if (json_generation_status != GetProtobufOkStatus()) {
       spvtools::Error(FuzzDiagnostic, nullptr, {},
                       "Error writing out transformations in JSON format");
       return 1;
diff --git a/tools/io.h b/tools/io.h
index f9cfd9d..83a85c1 100644
--- a/tools/io.h
+++ b/tools/io.h
@@ -20,45 +20,101 @@
 #include <cstring>
 #include <vector>
 
-// Appends the content from the file named as |filename| to |data|, assuming
-// each element in the file is of type |T|. The file is opened with the given
-// |mode|. If |filename| is nullptr or "-", reads from the standard input, but
-// reopened with the given mode. If any error occurs, writes error messages to
-// standard error and returns false.
+#if defined(SPIRV_WINDOWS)
+#include <fcntl.h>
+#include <io.h>
+
+#define SET_STDIN_TO_BINARY_MODE() _setmode(_fileno(stdin), O_BINARY);
+#define SET_STDIN_TO_TEXT_MODE() _setmode(_fileno(stdin), O_TEXT);
+#else
+#define SET_STDIN_TO_BINARY_MODE()
+#define SET_STDIN_TO_TEXT_MODE()
+#endif
+
+// Appends the contents of the |file| to |data|, assuming each element in the
+// file is of type |T|.
 template <typename T>
-bool ReadFile(const char* filename, const char* mode, std::vector<T>* data) {
+void ReadFile(FILE* file, std::vector<T>* data) {
+  if (file == nullptr) return;
+
   const int buf_size = 1024;
-  const bool use_file = filename && strcmp("-", filename);
-  if (FILE* fp =
-          (use_file ? fopen(filename, mode) : freopen(nullptr, mode, stdin))) {
-    T buf[buf_size];
-    while (size_t len = fread(buf, sizeof(T), buf_size, fp)) {
-      data->insert(data->end(), buf, buf + len);
-    }
-    if (ftell(fp) == -1L) {
-      if (ferror(fp)) {
-        fprintf(stderr, "error: error reading file '%s'\n", filename);
-        if (use_file) fclose(fp);
-        return false;
-      }
-    } else {
-      if (sizeof(T) != 1 && (ftell(fp) % sizeof(T))) {
-        fprintf(
-            stderr,
-            "error: file size should be a multiple of %zd; file '%s' corrupt\n",
-            sizeof(T), filename);
-        if (use_file) fclose(fp);
-        return false;
-      }
-    }
-    if (use_file) fclose(fp);
-  } else {
+  T buf[buf_size];
+  while (size_t len = fread(buf, sizeof(T), buf_size, file)) {
+    data->insert(data->end(), buf, buf + len);
+  }
+}
+
+// Returns true if |file| has encountered an error opening the file or reading
+// the file as a series of element of type |T|. If there was an error, writes an
+// error message to standard error.
+template <class T>
+bool WasFileCorrectlyRead(FILE* file, const char* filename) {
+  if (file == nullptr) {
     fprintf(stderr, "error: file does not exist '%s'\n", filename);
     return false;
   }
+
+  if (ftell(file) == -1L) {
+    if (ferror(file)) {
+      fprintf(stderr, "error: error reading file '%s'\n", filename);
+      return false;
+    }
+  } else {
+    if (sizeof(T) != 1 && (ftell(file) % sizeof(T))) {
+      fprintf(
+          stderr,
+          "error: file size should be a multiple of %zd; file '%s' corrupt\n",
+          sizeof(T), filename);
+      return false;
+    }
+  }
   return true;
 }
 
+// Appends the contents of the file named |filename| to |data|, assuming
+// each element in the file is of type |T|. The file is opened as a binary file
+// If |filename| is nullptr or "-", reads from the standard input, but
+// reopened as a binary file. If any error occurs, writes error messages to
+// standard error and returns false.
+template <typename T>
+bool ReadBinaryFile(const char* filename, std::vector<T>* data) {
+  const bool use_file = filename && strcmp("-", filename);
+  FILE* fp = nullptr;
+  if (use_file) {
+    fp = fopen(filename, "rb");
+  } else {
+    SET_STDIN_TO_BINARY_MODE();
+    fp = stdin;
+  }
+
+  ReadFile(fp, data);
+  bool succeeded = WasFileCorrectlyRead<T>(fp, filename);
+  if (use_file && fp) fclose(fp);
+  return succeeded;
+}
+
+// Appends the contents of the file named |filename| to |data|, assuming
+// each element in the file is of type |T|. The file is opened as a text file
+// If |filename| is nullptr or "-", reads from the standard input, but
+// reopened as a text file. If any error occurs, writes error messages to
+// standard error and returns false.
+template <typename T>
+bool ReadTextFile(const char* filename, std::vector<T>* data) {
+  const bool use_file = filename && strcmp("-", filename);
+  FILE* fp = nullptr;
+  if (use_file) {
+    fp = fopen(filename, "r");
+  } else {
+    SET_STDIN_TO_TEXT_MODE();
+    fp = stdin;
+  }
+
+  ReadFile(fp, data);
+  bool succeeded = WasFileCorrectlyRead<T>(fp, filename);
+  if (use_file && fp) fclose(fp);
+  return succeeded;
+}
+
 // Writes the given |data| into the file named as |filename| using the given
 // |mode|, assuming |data| is an array of |count| elements of type |T|. If
 // |filename| is nullptr or "-", writes to standard output. If any error occurs,
diff --git a/tools/link/linker.cpp b/tools/link/linker.cpp
index 1956f59..359e803 100644
--- a/tools/link/linker.cpp
+++ b/tools/link/linker.cpp
@@ -130,7 +130,7 @@
 
   std::vector<std::vector<uint32_t>> contents(inFiles.size());
   for (size_t i = 0u; i < inFiles.size(); ++i) {
-    if (!ReadFile<uint32_t>(inFiles[i], "rb", &contents[i])) return 1;
+    if (!ReadBinaryFile<uint32_t>(inFiles[i], &contents[i])) return 1;
   }
 
   const spvtools::MessageConsumer consumer = [](spv_message_level_t level,
diff --git a/tools/lint/lint.cpp b/tools/lint/lint.cpp
new file mode 100644
index 0000000..5c2a82a
--- /dev/null
+++ b/tools/lint/lint.cpp
@@ -0,0 +1,75 @@
+// Copyright (c) 2021 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <iostream>
+
+#include "source/opt/log.h"
+#include "spirv-tools/linter.hpp"
+#include "tools/io.h"
+#include "tools/util/cli_consumer.h"
+
+const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
+
+namespace {
+// Status and actions to perform after parsing command-line arguments.
+enum LintActions { LINT_CONTINUE, LINT_STOP };
+
+struct LintStatus {
+  LintActions action;
+  int code;
+};
+
+// Parses command-line flags. |argc| contains the number of command-line flags.
+// |argv| points to an array of strings holding the flags.
+//
+// On return, this function stores the name of the input program in |in_file|.
+// The return value indicates whether optimization should continue and a status
+// code indicating an error or success.
+LintStatus ParseFlags(int argc, const char** argv, const char** in_file) {
+  // TODO (dongja): actually parse flags, etc.
+  if (argc != 2) {
+    spvtools::Error(spvtools::utils::CLIMessageConsumer, nullptr, {},
+                    "expected exactly one argument: in_file");
+    return {LINT_STOP, 1};
+  }
+
+  *in_file = argv[1];
+
+  return {LINT_CONTINUE, 0};
+}
+}  // namespace
+
+int main(int argc, const char** argv) {
+  const char* in_file = nullptr;
+
+  spv_target_env target_env = kDefaultEnvironment;
+
+  spvtools::Linter linter(target_env);
+  linter.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
+
+  LintStatus status = ParseFlags(argc, argv, &in_file);
+
+  if (status.action == LINT_STOP) {
+    return status.code;
+  }
+
+  std::vector<uint32_t> binary;
+  if (!ReadBinaryFile(in_file, &binary)) {
+    return 1;
+  }
+
+  bool ok = linter.Run(binary.data(), binary.size());
+
+  return ok ? 0 : 1;
+}
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index e7e5899..04f81b8 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -143,6 +143,15 @@
                does not support RelaxedPrecision or ignores it. This pass also
                removes all RelaxedPrecision decorations.)");
   printf(R"(
+  --convert-to-sampled-image "<descriptor set>:<binding> ..."
+               convert images and/or samplers with the given pairs of descriptor
+               set and binding to sampled images. If a pair of an image and a
+               sampler have the same pair of descriptor set and binding that is
+               one of the given pairs, they will be converted to a sampled
+               image. In addition, if only an image or a sampler has the
+               descriptor set and binding that is one of the given pairs, it
+               will be converted to a sampled image.)");
+  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
@@ -154,6 +163,11 @@
                around known issues with some Vulkan drivers for initialize
                variables.)");
   printf(R"(
+  --replace-desc-array-access-using-var-index
+               Replaces accesses to descriptor arrays based on a variable index
+               with a switch that has a case for every possible value of the
+               index.)");
+  printf(R"(
   --descriptor-scalar-replacement
                Replaces every array variable |desc| that has a DescriptorSet
                and Binding decorations with a new variable for each element of
@@ -378,9 +392,12 @@
                Change the scope of private variables that are used in a single
                function to that function.)");
   printf(R"(
-  --reduce-load-size
+  --reduce-load-size[=<threshold>]
                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.  If the ratio
+               of the used components of the load is less than the <threshold>,
+               we replace the load.  <threshold> is a double type number.  If
+               it is bigger than 1.0, we always replaces the load.)");
   printf(R"(
   --redundancy-elimination
                Looks for instructions in the same function that compute the
@@ -406,6 +423,12 @@
                Removes duplicate types, decorations, capabilities and extension
                instructions.)");
   printf(R"(
+  --remove-unused-interface-variables
+               Removes variables referenced on the |OpEntryPoint| instruction 
+               that are not referenced in the entry point function or any function 
+               in its call tree.  Note that this could cause the shader interface 
+               to no longer match other shader stages.)");
+  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
@@ -490,6 +513,10 @@
                avoid triggering those bugs.
                Current workarounds: Avoid OpUnreachable in loops.)");
   printf(R"(
+  --workgroup-scalar-block-layout
+               Forwards this option to the validator.  See the validator help
+               for details.)");
+  printf(R"(
   --wrap-opkill
                Replaces all OpKill instructions in functions that can be called
                from a continue construct with a function call to a function
@@ -741,6 +768,8 @@
         validator_options->SetRelaxBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
         validator_options->SetScalarBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) {
+        validator_options->SetWorkgroupScalarBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
         validator_options->SetSkipBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
@@ -801,7 +830,7 @@
   }
 
   std::vector<uint32_t> binary;
-  if (!ReadFile<uint32_t>(in_file, "rb", &binary)) {
+  if (!ReadBinaryFile<uint32_t>(in_file, &binary)) {
     return 1;
   }
 
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 49a5efe..4447b35 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -318,7 +318,7 @@
   reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
 
   std::vector<uint32_t> binary_in;
-  if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
+  if (!ReadBinaryFile<uint32_t>(in_binary_file.c_str(), &binary_in)) {
     return 1;
   }
 
diff --git a/tools/sva/yarn.lock b/tools/sva/yarn.lock
index c46b901..afa11f6 100644
--- a/tools/sva/yarn.lock
+++ b/tools/sva/yarn.lock
@@ -694,9 +694,9 @@
     pump "^3.0.0"
 
 glob-parent@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
-  integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
   dependencies:
     is-glob "^4.0.1"
 
@@ -938,9 +938,9 @@
     path-exists "^3.0.0"
 
 lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14:
-  version "4.17.19"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
-  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
 log-symbols@2.2.0:
   version "2.2.0"
diff --git a/tools/val/val.cpp b/tools/val/val.cpp
index da63245..55321da 100644
--- a/tools/val/val.cpp
+++ b/tools/val/val.cpp
@@ -58,11 +58,14 @@
                                    uniform, storage buffer, and push constant layouts.  Scalar layout
                                    rules are more permissive than relaxed block layout so in effect
                                    this will override the --relax-block-layout option.
+  --workgroup-scalar-block-layout  Enable scalar block layout when checking Workgroup block layouts.
   --skip-block-layout              Skip checking standard uniform/storage buffer layout.
                                    Overrides any --relax-block-layout or --scalar-block-layout option.
   --relax-struct-store             Allow store from one struct type to a
                                    different type with compatible layout and
                                    members.
+  --allow-localsizeid              Allow use of the LocalSizeId decoration where it would otherwise not
+                                   be allowed by the target environment.
   --before-hlsl-legalization       Allows code patterns that are intended to be
                                    fixed by spirv-opt's legalization passes.
   --version                        Display validator version information.
@@ -148,8 +151,12 @@
         options.SetUniformBufferStandardLayout(true);
       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
         options.SetScalarBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) {
+        options.SetWorkgroupScalarBlockLayout(true);
       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
         options.SetSkipBlockLayout(true);
+      } else if (0 == strcmp(cur_arg, "--allow-localsizeid")) {
+        options.SetAllowLocalSizeId(true);
       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
         options.SetRelaxStructStore(true);
       } else if (0 == cur_arg[1]) {
@@ -183,7 +190,7 @@
   }
 
   std::vector<uint32_t> contents;
-  if (!ReadFile<uint32_t>(inFile, "rb", &contents)) return 1;
+  if (!ReadBinaryFile<uint32_t>(inFile, &contents)) return 1;
 
   spvtools::SpirvTools tools(target_env);
   tools.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index b15bc20..49892ee 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -36,8 +36,12 @@
            'André Perez Maselco',
            'Vasyl Teliman',
            'Advanced Micro Devices, Inc.',
-           'Stefano Milizia']
-CURRENT_YEAR='2020'
+           'Stefano Milizia',
+           'Alastair F. Donaldson',
+           'Mostafa Ashraf',
+           'Shiyu Liu',
+           'ZHOU He']
+CURRENT_YEAR='2021'
 
 YEARS = '(2014-2016|2015-2016|2015-2020|2016|2016-2017|2017|2017-2019|2018|2019|2020|2021)'
 COPYRIGHT_RE = re.compile(
@@ -128,7 +132,7 @@
         update_file = False
         for line in fileinput.input(file, inplace=1):
             emit = True
-            if state is 0:
+            if state == 0:
                 if COPYRIGHT_RE.search(line):
                     state = 1
                 elif skip(line):
@@ -139,7 +143,7 @@
                     sys.stdout.write(licensed)
                     # Assume there isn't a previous license notice.
                     state = 1
-            elif state is 1:
+            elif state == 1:
                 if MIT_BEGIN_RE.search(line):
                     state = 2
                     emit = False
@@ -147,7 +151,7 @@
                     # Assume an Apache license is preceded by a copyright
                     # notice.  So just emit it like the rest of the file.
                     state = 9
-            elif state is 2:
+            elif state == 2:
                 # Replace the MIT license with Apache 2
                 emit = False
                 if MIT_END_RE.search(line):
diff --git a/utils/check_symbol_exports.py b/utils/check_symbol_exports.py
index bcd77da..7795d72 100755
--- a/utils/check_symbol_exports.py
+++ b/utils/check_symbol_exports.py
@@ -12,7 +12,13 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-"""Checks names of global exports from a library."""
+"""Ensures that all externally visible functions in the library have an appropriate name
+
+Appropriate function names are:
+  - names starting with spv,
+  - anything in a namespace,
+  - functions added by the protobuf compiler,
+  - and weak definitions of new and delete."""
 
 import os.path
 import re
@@ -46,14 +52,16 @@
     exports are namespaced or begin with spv (in either C or C++ styles)
     then return 0.  Otherwise emit a message and return 1."""
 
-    # The pattern for a global symbol record
-    symbol_pattern = re.compile(r'^[0-aA-Fa-f]+ g *F \.text.*[0-9A-Fa-f]+ +(.*)')
+    # The pattern for an externally visible symbol record
+    symbol_pattern = re.compile(r'^[0-aA-Fa-f]+ +([wg]) *F \.text.*[0-9A-Fa-f]+ +(.*)')
 
     # Ok patterns are as follows, assuming Itanium name mangling:
     #   spv[A-Z]          :  extern "C" symbol starting with spv
     #   _ZN               :  something in a namespace
+    #   _ZSt              :  something in the standard namespace
+    #   _ZZN              :  something in a local scope and 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_])')
+    symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_ZSt|_ZZN|_Z[0-9]+spv[A-Z_])')
 
     # In addition, the following pattern allowlists global functions that are added
     # by the protobuf compiler:
@@ -61,18 +69,31 @@
     #   - InitDefaults_spvtoolsfuzz_2eproto()
     symbol_allowlist_pattern = re.compile(r'_Z[0-9]+(InitDefaults|AddDescriptors)_spvtoolsfuzz_2eprotov')
 
+    symbol_is_new_or_delete = re.compile(r'^(_Zna|_Znw|_Zdl|_Zda)')
+    # Compilaion for Arm has various thunks for constructors, destructors, vtables.
+    # They are weak.
+    symbol_is_thunk = re.compile(r'^_ZT')
+
+    # This occurs in NDK builds.
+    symbol_is_hidden = re.compile(r'^\.hidden ')
+
     seen = set()
     result = 0
     for line in command_output(['objdump', '-t', library], '.').split('\n'):
         match = symbol_pattern.search(line)
         if match:
-            symbol = match.group(1)
+            linkage = match.group(1)
+            symbol = match.group(2)
             if symbol not in seen:
                 seen.add(symbol)
                 #print("look at '{}'".format(symbol))
-                if not (symbol_allowlist_pattern.match(symbol) or symbol_ok_pattern.match(symbol)):
-                    print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol))
-                    result = 1
+                if not (symbol_is_new_or_delete.match(symbol) and linkage == 'w'):
+                    if not (symbol_is_thunk.match(symbol) and linkage == 'w'):
+                        if not (symbol_allowlist_pattern.match(symbol) or
+                                symbol_ok_pattern.match(symbol) or
+                                symbol_is_hidden.match(symbol)):
+                            print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol))
+                            result = 1
     return result
 
 
diff --git a/utils/generate_grammar_tables.py b/utils/generate_grammar_tables.py
index 2a67733..9ccf410 100755
--- a/utils/generate_grammar_tables.py
+++ b/utils/generate_grammar_tables.py
@@ -523,17 +523,17 @@
     enums = [generate_enum_operand_kind(e, exts) for e in enums]
     exts_arrays = generate_extension_arrays(exts)
 
-    # We have three operand kinds that requires their optional counterpart to
+    # We have a few operand kinds that require their optional counterpart to
     # exist in the operand info table.
-    three_optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess']
-    three_optional_enums = [e for e in enums if e[0] in three_optional_enums]
-    enums.extend(three_optional_enums)
+    optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess', 'PackedVectorFormat']
+    optional_enums = [e for e in enums if e[0] in optional_enums]
+    enums.extend(optional_enums)
 
     enum_kinds, enum_names, enum_entries = zip(*enums)
-    # Mark the last three as optional ones.
-    enum_quantifiers = [''] * (len(enums) - 3) + ['?'] * 3
+    # Mark the last few as optional ones.
+    enum_quantifiers = [''] * (len(enums) - len(optional_enums)) + ['?'] * len(optional_enums)
     # And we don't want redefinition of them.
-    enum_entries = enum_entries[:-3]
+    enum_entries = enum_entries[:-len(optional_enums)]
     enum_kinds = [convert_operand_kind(e)
                   for e in zip(enum_kinds, enum_quantifiers)]
     table_entries = zip(enum_kinds, enum_names, enum_names)
@@ -601,7 +601,7 @@
         '      return "{extension}";\n'
     function += ''.join([template.format(extension=extension)
                          for extension in extensions])
-    function += '  };\n\n  return "";\n}'
+    function += '  }\n\n  return "";\n}'
     return function
 
 
@@ -647,7 +647,7 @@
     function += '    case SpvCapabilityMax:\n' \
         '      assert(0 && "Attempting to convert SpvCapabilityMax to string");\n' \
         '      return "";\n'
-    function += '  };\n\n  return "";\n}'
+    function += '  }\n\n  return "";\n}'
     return function
 
 
diff --git a/utils/vscode/README.md b/utils/vscode/README.md
index bc02211..d7aa2b4 100644
--- a/utils/vscode/README.md
+++ b/utils/vscode/README.md
@@ -1,12 +1,20 @@
 # Visual Studio Code extension for SPIR-V disassembly files
 
-This directory holds a Visual Studio Code extension adding syntax highlighting for SPIR-V assembly files (`.spvasm`)
+This directory holds a Visual Studio Code language server extension for SPIR-V assembly files (`.spvasm`)
+
+The extension supports:
+* Syntax highlighting
+* Jump to definition
+* Find all references
+* Symbol renaming
+* Operand hover information
+* Formatting
 
 ## Dependencies
 
 In order to build and install the Visual Studio Code language server extension, you will need to install and have on your `PATH` the following dependencies:
 * [`npm`](https://www.npmjs.com/)
-* [`golang`](https://golang.org/)
+* [`golang 1.16+`](https://golang.org/)
 
 ## Installing (macOS / Linux)
 
diff --git a/utils/vscode/go.mod b/utils/vscode/go.mod
new file mode 100644
index 0000000..ea4901a
--- /dev/null
+++ b/utils/vscode/go.mod
@@ -0,0 +1,8 @@
+module github.com/KhronosGroup/SPIRV-Tools/utils/vscode
+
+go 1.16
+
+require (
+	github.com/pkg/errors v0.9.1
+	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
+)
diff --git a/utils/vscode/go.sum b/utils/vscode/go.sum
new file mode 100644
index 0000000..328c857
--- /dev/null
+++ b/utils/vscode/go.sum
@@ -0,0 +1,4 @@
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/utils/vscode/install.bat b/utils/vscode/install.bat
index 21a52ec..aa06fa9 100644
--- a/utils/vscode/install.bat
+++ b/utils/vscode/install.bat
@@ -23,7 +23,7 @@
 copy %ROOT_PATH%\package.json %EXT_PATH%
 copy %ROOT_PATH%\spirv.json %EXT_PATH%
 
-go build -o %EXT_PATH%\langsvr %ROOT_PATH%\src\langsvr.go
+go build -o %EXT_PATH%\langsvr.exe %ROOT_PATH%\src\langsvr.go
 
 @pushd %EXT_PATH%
 call npm install
diff --git a/utils/vscode/install.sh b/utils/vscode/install.sh
index 01fc914..de54c62 100755
--- a/utils/vscode/install.sh
+++ b/utils/vscode/install.sh
@@ -18,15 +18,17 @@
 EXT_PATH=~/.vscode/extensions/google.spirvls-0.0.1
 ROOT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 
-go run ${ROOT_PATH}/src/tools/gen-grammar.go --cache ${ROOT_PATH}/cache --template ${ROOT_PATH}/spirv.json.tmpl --out ${ROOT_PATH}/spirv.json
-go run ${ROOT_PATH}/src/tools/gen-grammar.go --cache ${ROOT_PATH}/cache --template ${ROOT_PATH}/src/schema/schema.go.tmpl --out ${ROOT_PATH}/src/schema/schema.go
+pushd ${ROOT_PATH}
+    go run ./src/tools/gen-grammar.go --cache ./cache --template ./spirv.json.tmpl --out ./spirv.json
+    go run ./src/tools/gen-grammar.go --cache ./cache --template ./src/schema/schema.go.tmpl --out ./src/schema/schema.go
 
-mkdir -p ${EXT_PATH}
-cp ${ROOT_PATH}/extension.js ${EXT_PATH}
-cp ${ROOT_PATH}/package.json ${EXT_PATH}
-cp ${ROOT_PATH}/spirv.json ${EXT_PATH}
+    mkdir -p ${EXT_PATH}
+    cp ./extension.js ${EXT_PATH}
+    cp ./package.json ${EXT_PATH}
+    cp ./spirv.json ${EXT_PATH}
 
-go build -o ${EXT_PATH}/langsvr ${ROOT_PATH}/src/langsvr.go
+    go build -o ${EXT_PATH}/langsvr ./src/langsvr.go
+popd
 
 cd ${EXT_PATH}
 npm install
diff --git a/utils/vscode/spirv.json b/utils/vscode/spirv.json
index 7999b52..2e88296 100644
--- a/utils/vscode/spirv.json
+++ b/utils/vscode/spirv.json
@@ -1,7 +1,7 @@
 {
 	"scopeName": "source.spirv",
 	"name": "SPIR-V",
-	"comment": "Generated by gen-grammar.go --template=../../spirv.json.tmpl --out=../../spirv.json. Do not modify this file directly.",
+	"comment": "Generated by gen-grammar.go --template=./spirv.json.tmpl --out=./spirv.json. Do not modify this file directly.",
 	"patterns": [
 		{ "include": "#BitEnum_ImageOperands" },
 		{ "include": "#BitEnum_FPFastMathMode" },
@@ -11,6 +11,7 @@
 		{ "include": "#BitEnum_MemorySemantics" },
 		{ "include": "#BitEnum_MemoryAccess" },
 		{ "include": "#BitEnum_KernelProfilingInfo" },
+		{ "include": "#BitEnum_RayFlags" },
 		{ "include": "#ValueEnum_SourceLanguage" },
 		{ "include": "#ValueEnum_ExecutionModel" },
 		{ "include": "#ValueEnum_AddressingModel" },
@@ -33,6 +34,9 @@
 		{ "include": "#ValueEnum_GroupOperation" },
 		{ "include": "#ValueEnum_KernelEnqueueFlags" },
 		{ "include": "#ValueEnum_Capability" },
+		{ "include": "#ValueEnum_RayQueryIntersection" },
+		{ "include": "#ValueEnum_RayQueryCommittedIntersectionType" },
+		{ "include": "#ValueEnum_RayQueryCandidateIntersectionType" },
 		{ "include": "#BitEnum_DebugInfoFlags" },
 		{ "include": "#ValueEnum_DebugBaseTypeAttributeEncoding" },
 		{ "include": "#ValueEnum_DebugCompositeType" },
@@ -80,12 +84,16 @@
 			"match": "\\b(None|CmdExecTime)\\b",
 			"name": "keyword.spirv"
 		},
+		"BitEnum_RayFlags": {
+			"match": "\\b(NoneKHR|OpaqueKHR|NoOpaqueKHR|TerminateOnFirstHitKHR|SkipClosestHitShaderKHR|CullBackFacingTrianglesKHR|CullFrontFacingTrianglesKHR|CullOpaqueKHR|CullNoOpaqueKHR|SkipTrianglesKHR|SkipAABBsKHR)\\b",
+			"name": "keyword.spirv"
+		},
 		"ValueEnum_SourceLanguage": {
 			"match": "\\b(Unknown|ESSL|GLSL|OpenCL_C|OpenCL_CPP|HLSL)\\b",
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_ExecutionModel": {
-			"match": "\\b(Vertex|TessellationControl|TessellationEvaluation|Geometry|Fragment|GLCompute|Kernel|TaskNV|MeshNV|RayGenerationNV|IntersectionNV|AnyHitNV|ClosestHitNV|MissNV|CallableNV)\\b",
+			"match": "\\b(Vertex|TessellationControl|TessellationEvaluation|Geometry|Fragment|GLCompute|Kernel|TaskNV|MeshNV|RayGenerationNV|RayGenerationKHR|IntersectionNV|IntersectionKHR|AnyHitNV|AnyHitKHR|ClosestHitNV|ClosestHitKHR|MissNV|MissKHR|CallableNV|CallableKHR)\\b",
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_AddressingModel": {
@@ -101,7 +109,7 @@
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_StorageClass": {
-			"match": "\\b(UniformConstant|Input|Uniform|Output|Workgroup|CrossWorkgroup|Private|Function|Generic|PushConstant|AtomicCounter|Image|StorageBuffer|CallableDataNV|IncomingCallableDataNV|RayPayloadNV|HitAttributeNV|IncomingRayPayloadNV|ShaderRecordBufferNV|PhysicalStorageBuffer|PhysicalStorageBufferEXT)\\b",
+			"match": "\\b(UniformConstant|Input|Uniform|Output|Workgroup|CrossWorkgroup|Private|Function|Generic|PushConstant|AtomicCounter|Image|StorageBuffer|CallableDataNV|CallableDataKHR|IncomingCallableDataNV|IncomingCallableDataKHR|RayPayloadNV|RayPayloadKHR|HitAttributeNV|HitAttributeKHR|IncomingRayPayloadNV|IncomingRayPayloadKHR|ShaderRecordBufferNV|ShaderRecordBufferKHR|PhysicalStorageBuffer|PhysicalStorageBufferEXT)\\b",
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_Dim": {
@@ -149,11 +157,11 @@
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_BuiltIn": {
-			"match": "\\b(Position|PointSize|ClipDistance|CullDistance|VertexId|InstanceId|PrimitiveId|InvocationId|Layer|ViewportIndex|TessLevelOuter|TessLevelInner|TessCoord|PatchVertices|FragCoord|PointCoord|FrontFacing|SampleId|SamplePosition|SampleMask|FragDepth|HelperInvocation|NumWorkgroups|WorkgroupSize|WorkgroupId|LocalInvocationId|GlobalInvocationId|LocalInvocationIndex|WorkDim|GlobalSize|EnqueuedWorkgroupSize|GlobalOffset|GlobalLinearId|SubgroupSize|SubgroupMaxSize|NumSubgroups|NumEnqueuedSubgroups|SubgroupId|SubgroupLocalInvocationId|VertexIndex|InstanceIndex|SubgroupEqMask|SubgroupGeMask|SubgroupGtMask|SubgroupLeMask|SubgroupLtMask|SubgroupEqMaskKHR|SubgroupGeMaskKHR|SubgroupGtMaskKHR|SubgroupLeMaskKHR|SubgroupLtMaskKHR|BaseVertex|BaseInstance|DrawIndex|DeviceIndex|ViewIndex|BaryCoordNoPerspAMD|BaryCoordNoPerspCentroidAMD|BaryCoordNoPerspSampleAMD|BaryCoordSmoothAMD|BaryCoordSmoothCentroidAMD|BaryCoordSmoothSampleAMD|BaryCoordPullModelAMD|FragStencilRefEXT|ViewportMaskNV|SecondaryPositionNV|SecondaryViewportMaskNV|PositionPerViewNV|ViewportMaskPerViewNV|FullyCoveredEXT|TaskCountNV|PrimitiveCountNV|PrimitiveIndicesNV|ClipDistancePerViewNV|CullDistancePerViewNV|LayerPerViewNV|MeshViewCountNV|MeshViewIndicesNV|BaryCoordNV|BaryCoordNoPerspNV|FragSizeEXT|FragmentSizeNV|FragInvocationCountEXT|InvocationsPerPixelNV|LaunchIdNV|LaunchSizeNV|WorldRayOriginNV|WorldRayDirectionNV|ObjectRayOriginNV|ObjectRayDirectionNV|RayTminNV|RayTmaxNV|InstanceCustomIndexNV|ObjectToWorldNV|WorldToObjectNV|HitTNV|HitKindNV|IncomingRayFlagsNV|WarpsPerSMNV|SMCountNV|WarpIDNV|SMIDNV)\\b",
+			"match": "\\b(Position|PointSize|ClipDistance|CullDistance|VertexId|InstanceId|PrimitiveId|InvocationId|Layer|ViewportIndex|TessLevelOuter|TessLevelInner|TessCoord|PatchVertices|FragCoord|PointCoord|FrontFacing|SampleId|SamplePosition|SampleMask|FragDepth|HelperInvocation|NumWorkgroups|WorkgroupSize|WorkgroupId|LocalInvocationId|GlobalInvocationId|LocalInvocationIndex|WorkDim|GlobalSize|EnqueuedWorkgroupSize|GlobalOffset|GlobalLinearId|SubgroupSize|SubgroupMaxSize|NumSubgroups|NumEnqueuedSubgroups|SubgroupId|SubgroupLocalInvocationId|VertexIndex|InstanceIndex|SubgroupEqMask|SubgroupGeMask|SubgroupGtMask|SubgroupLeMask|SubgroupLtMask|SubgroupEqMaskKHR|SubgroupGeMaskKHR|SubgroupGtMaskKHR|SubgroupLeMaskKHR|SubgroupLtMaskKHR|BaseVertex|BaseInstance|DrawIndex|DeviceIndex|ViewIndex|BaryCoordNoPerspAMD|BaryCoordNoPerspCentroidAMD|BaryCoordNoPerspSampleAMD|BaryCoordSmoothAMD|BaryCoordSmoothCentroidAMD|BaryCoordSmoothSampleAMD|BaryCoordPullModelAMD|FragStencilRefEXT|ViewportMaskNV|SecondaryPositionNV|SecondaryViewportMaskNV|PositionPerViewNV|ViewportMaskPerViewNV|FullyCoveredEXT|TaskCountNV|PrimitiveCountNV|PrimitiveIndicesNV|ClipDistancePerViewNV|CullDistancePerViewNV|LayerPerViewNV|MeshViewCountNV|MeshViewIndicesNV|BaryCoordNV|BaryCoordNoPerspNV|FragSizeEXT|FragmentSizeNV|FragInvocationCountEXT|InvocationsPerPixelNV|LaunchIdNV|LaunchIdKHR|LaunchSizeNV|LaunchSizeKHR|WorldRayOriginNV|WorldRayOriginKHR|WorldRayDirectionNV|WorldRayDirectionKHR|ObjectRayOriginNV|ObjectRayOriginKHR|ObjectRayDirectionNV|ObjectRayDirectionKHR|RayTminNV|RayTminKHR|RayTmaxNV|RayTmaxKHR|InstanceCustomIndexNV|InstanceCustomIndexKHR|ObjectToWorldNV|ObjectToWorldKHR|WorldToObjectNV|WorldToObjectKHR|HitTNV|HitTKHR|HitKindNV|HitKindKHR|IncomingRayFlagsNV|IncomingRayFlagsKHR|RayGeometryIndexKHR|WarpsPerSMNV|SMCountNV|WarpIDNV|SMIDNV)\\b",
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_Scope": {
-			"match": "\\b(CrossDevice|Device|Workgroup|Subgroup|Invocation|QueueFamily|QueueFamilyKHR)\\b",
+			"match": "\\b(CrossDevice|Device|Workgroup|Subgroup|Invocation|QueueFamily|QueueFamilyKHR|ShaderCallKHR)\\b",
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_GroupOperation": {
@@ -165,7 +173,19 @@
 			"name": "keyword.spirv"
 		},
 		"ValueEnum_Capability": {
-			"match": "\\b(Matrix|Shader|Geometry|Tessellation|Addresses|Linkage|Kernel|Vector16|Float16Buffer|Float16|Float64|Int64|Int64Atomics|ImageBasic|ImageReadWrite|ImageMipmap|Pipes|Groups|DeviceEnqueue|LiteralSampler|AtomicStorage|Int16|TessellationPointSize|GeometryPointSize|ImageGatherExtended|StorageImageMultisample|UniformBufferArrayDynamicIndexing|SampledImageArrayDynamicIndexing|StorageBufferArrayDynamicIndexing|StorageImageArrayDynamicIndexing|ClipDistance|CullDistance|ImageCubeArray|SampleRateShading|ImageRect|SampledRect|GenericPointer|Int8|InputAttachment|SparseResidency|MinLod|Sampled1D|Image1D|SampledCubeArray|SampledBuffer|ImageBuffer|ImageMSArray|StorageImageExtendedFormats|ImageQuery|DerivativeControl|InterpolationFunction|TransformFeedback|GeometryStreams|StorageImageReadWithoutFormat|StorageImageWriteWithoutFormat|MultiViewport|SubgroupDispatch|NamedBarrier|PipeStorage|GroupNonUniform|GroupNonUniformVote|GroupNonUniformArithmetic|GroupNonUniformBallot|GroupNonUniformShuffle|GroupNonUniformShuffleRelative|GroupNonUniformClustered|GroupNonUniformQuad|ShaderLayer|ShaderViewportIndex|SubgroupBallotKHR|DrawParameters|SubgroupVoteKHR|StorageBuffer16BitAccess|StorageUniformBufferBlock16|UniformAndStorageBuffer16BitAccess|StorageUniform16|StoragePushConstant16|StorageInputOutput16|DeviceGroup|MultiView|VariablePointersStorageBuffer|VariablePointers|AtomicStorageOps|SampleMaskPostDepthCoverage|StorageBuffer8BitAccess|UniformAndStorageBuffer8BitAccess|StoragePushConstant8|DenormPreserve|DenormFlushToZero|SignedZeroInfNanPreserve|RoundingModeRTE|RoundingModeRTZ|Float16ImageAMD|ImageGatherBiasLodAMD|FragmentMaskAMD|StencilExportEXT|ImageReadWriteLodAMD|ShaderClockKHR|SampleMaskOverrideCoverageNV|GeometryShaderPassthroughNV|ShaderViewportIndexLayerEXT|ShaderViewportIndexLayerNV|ShaderViewportMaskNV|ShaderStereoViewNV|PerViewAttributesNV|FragmentFullyCoveredEXT|MeshShadingNV|ImageFootprintNV|FragmentBarycentricNV|ComputeDerivativeGroupQuadsNV|FragmentDensityEXT|ShadingRateNV|GroupNonUniformPartitionedNV|ShaderNonUniform|ShaderNonUniformEXT|RuntimeDescriptorArray|RuntimeDescriptorArrayEXT|InputAttachmentArrayDynamicIndexing|InputAttachmentArrayDynamicIndexingEXT|UniformTexelBufferArrayDynamicIndexing|UniformTexelBufferArrayDynamicIndexingEXT|StorageTexelBufferArrayDynamicIndexing|StorageTexelBufferArrayDynamicIndexingEXT|UniformBufferArrayNonUniformIndexing|UniformBufferArrayNonUniformIndexingEXT|SampledImageArrayNonUniformIndexing|SampledImageArrayNonUniformIndexingEXT|StorageBufferArrayNonUniformIndexing|StorageBufferArrayNonUniformIndexingEXT|StorageImageArrayNonUniformIndexing|StorageImageArrayNonUniformIndexingEXT|InputAttachmentArrayNonUniformIndexing|InputAttachmentArrayNonUniformIndexingEXT|UniformTexelBufferArrayNonUniformIndexing|UniformTexelBufferArrayNonUniformIndexingEXT|StorageTexelBufferArrayNonUniformIndexing|StorageTexelBufferArrayNonUniformIndexingEXT|RayTracingNV|VulkanMemoryModel|VulkanMemoryModelKHR|VulkanMemoryModelDeviceScope|VulkanMemoryModelDeviceScopeKHR|PhysicalStorageBufferAddresses|PhysicalStorageBufferAddressesEXT|ComputeDerivativeGroupLinearNV|CooperativeMatrixNV|FragmentShaderSampleInterlockEXT|FragmentShaderShadingRateInterlockEXT|ShaderSMBuiltinsNV|FragmentShaderPixelInterlockEXT|DemoteToHelperInvocationEXT|SubgroupShuffleINTEL|SubgroupBufferBlockIOINTEL|SubgroupImageBlockIOINTEL|SubgroupImageMediaBlockIOINTEL|IntegerFunctions2INTEL|SubgroupAvcMotionEstimationINTEL|SubgroupAvcMotionEstimationIntraINTEL|SubgroupAvcMotionEstimationChromaINTEL)\\b",
+			"match": "\\b(Matrix|Shader|Geometry|Tessellation|Addresses|Linkage|Kernel|Vector16|Float16Buffer|Float16|Float64|Int64|Int64Atomics|ImageBasic|ImageReadWrite|ImageMipmap|Pipes|Groups|DeviceEnqueue|LiteralSampler|AtomicStorage|Int16|TessellationPointSize|GeometryPointSize|ImageGatherExtended|StorageImageMultisample|UniformBufferArrayDynamicIndexing|SampledImageArrayDynamicIndexing|StorageBufferArrayDynamicIndexing|StorageImageArrayDynamicIndexing|ClipDistance|CullDistance|ImageCubeArray|SampleRateShading|ImageRect|SampledRect|GenericPointer|Int8|InputAttachment|SparseResidency|MinLod|Sampled1D|Image1D|SampledCubeArray|SampledBuffer|ImageBuffer|ImageMSArray|StorageImageExtendedFormats|ImageQuery|DerivativeControl|InterpolationFunction|TransformFeedback|GeometryStreams|StorageImageReadWithoutFormat|StorageImageWriteWithoutFormat|MultiViewport|SubgroupDispatch|NamedBarrier|PipeStorage|GroupNonUniform|GroupNonUniformVote|GroupNonUniformArithmetic|GroupNonUniformBallot|GroupNonUniformShuffle|GroupNonUniformShuffleRelative|GroupNonUniformClustered|GroupNonUniformQuad|ShaderLayer|ShaderViewportIndex|SubgroupBallotKHR|DrawParameters|SubgroupVoteKHR|StorageBuffer16BitAccess|StorageUniformBufferBlock16|UniformAndStorageBuffer16BitAccess|StorageUniform16|StoragePushConstant16|StorageInputOutput16|DeviceGroup|MultiView|VariablePointersStorageBuffer|VariablePointers|AtomicStorageOps|SampleMaskPostDepthCoverage|StorageBuffer8BitAccess|UniformAndStorageBuffer8BitAccess|StoragePushConstant8|DenormPreserve|DenormFlushToZero|SignedZeroInfNanPreserve|RoundingModeRTE|RoundingModeRTZ|RayQueryProvisionalKHR|RayTraversalPrimitiveCullingProvisionalKHR|Float16ImageAMD|ImageGatherBiasLodAMD|FragmentMaskAMD|StencilExportEXT|ImageReadWriteLodAMD|ShaderClockKHR|SampleMaskOverrideCoverageNV|GeometryShaderPassthroughNV|ShaderViewportIndexLayerEXT|ShaderViewportIndexLayerNV|ShaderViewportMaskNV|ShaderStereoViewNV|PerViewAttributesNV|FragmentFullyCoveredEXT|MeshShadingNV|ImageFootprintNV|FragmentBarycentricNV|ComputeDerivativeGroupQuadsNV|FragmentDensityEXT|ShadingRateNV|GroupNonUniformPartitionedNV|ShaderNonUniform|ShaderNonUniformEXT|RuntimeDescriptorArray|RuntimeDescriptorArrayEXT|InputAttachmentArrayDynamicIndexing|InputAttachmentArrayDynamicIndexingEXT|UniformTexelBufferArrayDynamicIndexing|UniformTexelBufferArrayDynamicIndexingEXT|StorageTexelBufferArrayDynamicIndexing|StorageTexelBufferArrayDynamicIndexingEXT|UniformBufferArrayNonUniformIndexing|UniformBufferArrayNonUniformIndexingEXT|SampledImageArrayNonUniformIndexing|SampledImageArrayNonUniformIndexingEXT|StorageBufferArrayNonUniformIndexing|StorageBufferArrayNonUniformIndexingEXT|StorageImageArrayNonUniformIndexing|StorageImageArrayNonUniformIndexingEXT|InputAttachmentArrayNonUniformIndexing|InputAttachmentArrayNonUniformIndexingEXT|UniformTexelBufferArrayNonUniformIndexing|UniformTexelBufferArrayNonUniformIndexingEXT|StorageTexelBufferArrayNonUniformIndexing|StorageTexelBufferArrayNonUniformIndexingEXT|RayTracingNV|VulkanMemoryModel|VulkanMemoryModelKHR|VulkanMemoryModelDeviceScope|VulkanMemoryModelDeviceScopeKHR|PhysicalStorageBufferAddresses|PhysicalStorageBufferAddressesEXT|ComputeDerivativeGroupLinearNV|RayTracingProvisionalKHR|CooperativeMatrixNV|FragmentShaderSampleInterlockEXT|FragmentShaderShadingRateInterlockEXT|ShaderSMBuiltinsNV|FragmentShaderPixelInterlockEXT|DemoteToHelperInvocationEXT|SubgroupShuffleINTEL|SubgroupBufferBlockIOINTEL|SubgroupImageBlockIOINTEL|SubgroupImageMediaBlockIOINTEL|IntegerFunctions2INTEL|SubgroupAvcMotionEstimationINTEL|SubgroupAvcMotionEstimationIntraINTEL|SubgroupAvcMotionEstimationChromaINTEL)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_RayQueryIntersection": {
+			"match": "\\b(RayQueryCandidateIntersectionKHR|RayQueryCommittedIntersectionKHR)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_RayQueryCommittedIntersectionType": {
+			"match": "\\b(RayQueryCommittedIntersectionNoneKHR|RayQueryCommittedIntersectionTriangleKHR|RayQueryCommittedIntersectionGeneratedKHR)\\b",
+			"name": "keyword.spirv"
+		},
+		"ValueEnum_RayQueryCandidateIntersectionType": {
+			"match": "\\b(RayQueryCandidateIntersectionTriangleKHR|RayQueryCandidateIntersectionAABBKHR)\\b",
 			"name": "keyword.spirv"
 		},
 		"BitEnum_DebugInfoFlags": {
diff --git a/utils/vscode/src/langsvr.go b/utils/vscode/src/langsvr.go
index d1b80dc..b76e35f 100644
--- a/utils/vscode/src/langsvr.go
+++ b/utils/vscode/src/langsvr.go
@@ -28,11 +28,15 @@
 	"sync"
 	"unicode/utf8"
 
-	"./parser"
-	"./schema"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/parser"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/schema"
 
-	"./lsp/jsonrpc2"
-	lsp "./lsp/protocol"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/jsonrpc2"
+	lsp "github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/protocol"
+)
+
+const (
+	enableDebugLogging = false
 )
 
 // rSpy is a reader 'spy' that wraps an io.Reader, and logs all data that passes
@@ -63,12 +67,13 @@
 
 // main entry point.
 func main() {
-	// create a log file in the executable's directory.
-	if logfile, err := os.Create(path.Join(path.Dir(os.Args[0]), "log.txt")); err == nil {
-		defer logfile.Close()
-		log.SetOutput(logfile)
-	} else {
-		log.SetOutput(ioutil.Discard)
+	log.SetOutput(ioutil.Discard)
+	if enableDebugLogging {
+		// create a log file in the executable's directory.
+		if logfile, err := os.Create(path.Join(path.Dir(os.Args[0]), "log.txt")); err == nil {
+			defer logfile.Close()
+			log.SetOutput(logfile)
+		}
 	}
 
 	log.Println("language server started")
@@ -407,13 +412,15 @@
 			}
 		}
 
-		// Every good file ends with a new line.
-		sb.WriteString("\n")
+		formatted := sb.String()
+
+		// Every good file ends with a single new line.
+		formatted = strings.TrimRight(formatted, "\n") + "\n"
 
 		return []lsp.TextEdit{
-			lsp.TextEdit{
+			{
 				Range:   rangeToLSP(f.fullRange),
-				NewText: sb.String(),
+				NewText: formatted,
 			},
 		}, nil
 	}
diff --git a/utils/vscode/src/lsp/protocol/log.go b/utils/vscode/src/lsp/protocol/log.go
index f245881..2fd7bbb 100644
--- a/utils/vscode/src/lsp/protocol/log.go
+++ b/utils/vscode/src/lsp/protocol/log.go
@@ -23,7 +23,7 @@
 	"sync"
 	"time"
 
-	"../jsonrpc2"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/jsonrpc2"
 )
 
 type loggingStream struct {
diff --git a/utils/vscode/src/lsp/protocol/protocol.go b/utils/vscode/src/lsp/protocol/protocol.go
index e396c83..886b0aa 100644
--- a/utils/vscode/src/lsp/protocol/protocol.go
+++ b/utils/vscode/src/lsp/protocol/protocol.go
@@ -19,7 +19,7 @@
 	"encoding/json"
 	"log"
 
-	"../jsonrpc2"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/jsonrpc2"
 )
 
 const (
diff --git a/utils/vscode/src/lsp/protocol/span.go b/utils/vscode/src/lsp/protocol/span.go
index 33cc2a6..799c228 100644
--- a/utils/vscode/src/lsp/protocol/span.go
+++ b/utils/vscode/src/lsp/protocol/span.go
@@ -19,7 +19,8 @@
 import (
 	"fmt"
 
-	"../span"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/span"
+
 	errors "golang.org/x/xerrors"
 )
 
diff --git a/utils/vscode/src/lsp/protocol/tsclient.go b/utils/vscode/src/lsp/protocol/tsclient.go
index 2f9beef..f68d63d 100644
--- a/utils/vscode/src/lsp/protocol/tsclient.go
+++ b/utils/vscode/src/lsp/protocol/tsclient.go
@@ -19,7 +19,7 @@
 	"encoding/json"
 	"log"
 
-	"../jsonrpc2"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/jsonrpc2"
 )
 
 type Client interface {
diff --git a/utils/vscode/src/lsp/protocol/tsserver.go b/utils/vscode/src/lsp/protocol/tsserver.go
index d760501..37e8c6a 100644
--- a/utils/vscode/src/lsp/protocol/tsserver.go
+++ b/utils/vscode/src/lsp/protocol/tsserver.go
@@ -19,7 +19,7 @@
 	"encoding/json"
 	"log"
 
-	"../jsonrpc2"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/lsp/jsonrpc2"
 )
 
 type Server interface {
diff --git a/utils/vscode/src/parser/parser.go b/utils/vscode/src/parser/parser.go
index 260a616..cc6f333 100644
--- a/utils/vscode/src/parser/parser.go
+++ b/utils/vscode/src/parser/parser.go
@@ -23,7 +23,7 @@
 	"unicode"
 	"unicode/utf8"
 
-	"../schema"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/schema"
 )
 
 // Type is an enumerator of token types.
diff --git a/utils/vscode/src/schema/schema.go b/utils/vscode/src/schema/schema.go
index 0fde3fe..ed02de4 100755
--- a/utils/vscode/src/schema/schema.go
+++ b/utils/vscode/src/schema/schema.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Generated by gen-grammar.go --template=../schema/schema.go.tmpl --out=../schema/schema.go
+// Generated by gen-grammar.go --template=./src/schema/schema.go.tmpl --out=./src/schema/schema.go
 // Do not modify this file directly.
 
 package schema
@@ -477,7 +477,7 @@
 		"OpTraceRayKHR": OpTraceRayKHR,
 		"OpTypeAccelerationStructureNV": OpTypeAccelerationStructureNV,
 		"OpTypeAccelerationStructureKHR": OpTypeAccelerationStructureKHR,
-		"OpTypeRayQueryKHR": OpTypeRayQueryKHR,
+		"OpTypeRayQueryProvisionalKHR": OpTypeRayQueryProvisionalKHR,
 		"OpRayQueryInitializeKHR": OpRayQueryInitializeKHR,
 		"OpRayQueryTerminateKHR": OpRayQueryTerminateKHR,
 		"OpRayQueryGenerateIntersectionKHR": OpRayQueryGenerateIntersectionKHR,
@@ -10807,8 +10807,8 @@
 			}, 
 		},
 	}
-	OpTypeRayQueryKHR = &Opcode {
-		Opname:   "OpTypeRayQueryKHR",
+	OpTypeRayQueryProvisionalKHR = &Opcode {
+		Opname:   "OpTypeRayQueryProvisionalKHR",
 		Class:    "Reserved",
 		Opcode:   4472,
 		Operands: []Operand {
@@ -20183,77 +20183,77 @@
 			Enumerant{
 				Enumerant:    "NoneKHR",
 				Value:        0x0000,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "OpaqueKHR",
 				Value:        0x0001,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "NoOpaqueKHR",
 				Value:        0x0002,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "TerminateOnFirstHitKHR",
 				Value:        0x0004,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "SkipClosestHitShaderKHR",
 				Value:        0x0008,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "CullBackFacingTrianglesKHR",
 				Value:        0x0010,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "CullFrontFacingTrianglesKHR",
 				Value:        0x0020,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "CullOpaqueKHR",
 				Value:        0x0040,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "CullNoOpaqueKHR",
 				Value:        0x0080,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "SkipTrianglesKHR",
 				Value:        0x0100,
-				Capabilities: []string{"RayTraversalPrimitiveCullingKHR",},
+				Capabilities: []string{"RayTraversalPrimitiveCullingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "SkipAABBsKHR",
 				Value:        0x0200,
-				Capabilities: []string{"RayTraversalPrimitiveCullingKHR",},
+				Capabilities: []string{"RayTraversalPrimitiveCullingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
@@ -20379,84 +20379,84 @@
 			Enumerant{
 				Enumerant:    "RayGenerationNV",
 				Value:        5313,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayGenerationKHR",
 				Value:        5313,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IntersectionNV",
 				Value:        5314,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IntersectionKHR",
 				Value:        5314,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "AnyHitNV",
 				Value:        5315,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "AnyHitKHR",
 				Value:        5315,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ClosestHitNV",
 				Value:        5316,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ClosestHitKHR",
 				Value:        5316,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "MissNV",
 				Value:        5317,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "MissKHR",
 				Value:        5317,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "CallableNV",
 				Value:        5318,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "CallableKHR",
 				Value:        5318,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
@@ -21044,84 +21044,84 @@
 			Enumerant{
 				Enumerant:    "CallableDataNV",
 				Value:        5328,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "CallableDataKHR",
 				Value:        5328,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IncomingCallableDataNV",
 				Value:        5329,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IncomingCallableDataKHR",
 				Value:        5329,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayPayloadNV",
 				Value:        5338,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayPayloadKHR",
 				Value:        5338,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "HitAttributeNV",
 				Value:        5339,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "HitAttributeKHR",
 				Value:        5339,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IncomingRayPayloadNV",
 				Value:        5342,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IncomingRayPayloadKHR",
 				Value:        5342,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ShaderRecordBufferNV",
 				Value:        5343,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ShaderRecordBufferKHR",
 				Value:        5343,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
@@ -22507,7 +22507,7 @@
 			Enumerant{
 				Enumerant:    "PrimitiveId",
 				Value:        7,
-				Capabilities: []string{"Geometry","Tessellation","RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"Geometry","Tessellation","RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
@@ -23053,203 +23053,203 @@
 			Enumerant{
 				Enumerant:    "LaunchIdNV",
 				Value:        5319,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "LaunchIdKHR",
 				Value:        5319,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "LaunchSizeNV",
 				Value:        5320,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "LaunchSizeKHR",
 				Value:        5320,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "WorldRayOriginNV",
 				Value:        5321,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "WorldRayOriginKHR",
 				Value:        5321,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "WorldRayDirectionNV",
 				Value:        5322,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "WorldRayDirectionKHR",
 				Value:        5322,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ObjectRayOriginNV",
 				Value:        5323,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ObjectRayOriginKHR",
 				Value:        5323,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ObjectRayDirectionNV",
 				Value:        5324,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ObjectRayDirectionKHR",
 				Value:        5324,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayTminNV",
 				Value:        5325,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayTminKHR",
 				Value:        5325,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayTmaxNV",
 				Value:        5326,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayTmaxKHR",
 				Value:        5326,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "InstanceCustomIndexNV",
 				Value:        5327,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "InstanceCustomIndexKHR",
 				Value:        5327,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ObjectToWorldNV",
 				Value:        5330,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "ObjectToWorldKHR",
 				Value:        5330,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "WorldToObjectNV",
 				Value:        5331,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "WorldToObjectKHR",
 				Value:        5331,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "HitTNV",
 				Value:        5332,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "HitTKHR",
 				Value:        5332,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "HitKindNV",
 				Value:        5333,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "HitKindKHR",
 				Value:        5333,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IncomingRayFlagsNV",
 				Value:        5351,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "IncomingRayFlagsKHR",
 				Value:        5351,
-				Capabilities: []string{"RayTracingNV","RayTracingKHR",},
+				Capabilities: []string{"RayTracingNV","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
 				Enumerant:    "RayGeometryIndexKHR",
 				Value:        5352,
-				Capabilities: []string{"RayTracingKHR",},
+				Capabilities: []string{"RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
@@ -23340,7 +23340,7 @@
 			Enumerant{
 				Enumerant:    "ShaderCallKHR",
 				Value:        6,
-				Capabilities: []string{"RayTracingKHR",},
+				Capabilities: []string{"RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
@@ -24080,16 +24080,16 @@
 				Version:      "1.4",
 			},
 			Enumerant{
-				Enumerant:    "RayQueryKHR",
+				Enumerant:    "RayQueryProvisionalKHR",
 				Value:        4471,
 				Capabilities: []string{"Shader",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
 			Enumerant{
-				Enumerant:    "RayTraversalPrimitiveCullingKHR",
+				Enumerant:    "RayTraversalPrimitiveCullingProvisionalKHR",
 				Value:        4478,
-				Capabilities: []string{"RayQueryKHR","RayTracingKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR","RayTracingProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "None",
 			},
@@ -24465,7 +24465,7 @@
 				Version:      "None",
 			},
 			Enumerant{
-				Enumerant:    "RayTracingKHR",
+				Enumerant:    "RayTracingProvisionalKHR",
 				Value:        5353,
 				Capabilities: []string{"Shader",},
 				Parameters:   []Parameter{},
@@ -24579,14 +24579,14 @@
 			Enumerant{
 				Enumerant:    "RayQueryCandidateIntersectionKHR",
 				Value:        0,
-				Capabilities: []string{"RayQueryKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "RayQueryCommittedIntersectionKHR",
 				Value:        1,
-				Capabilities: []string{"RayQueryKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
@@ -24600,21 +24600,21 @@
 			Enumerant{
 				Enumerant:    "RayQueryCommittedIntersectionNoneKHR",
 				Value:        0,
-				Capabilities: []string{"RayQueryKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "RayQueryCommittedIntersectionTriangleKHR",
 				Value:        1,
-				Capabilities: []string{"RayQueryKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "RayQueryCommittedIntersectionGeneratedKHR",
 				Value:        2,
-				Capabilities: []string{"RayQueryKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
@@ -24628,14 +24628,14 @@
 			Enumerant{
 				Enumerant:    "RayQueryCandidateIntersectionTriangleKHR",
 				Value:        0,
-				Capabilities: []string{"RayQueryKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
 			Enumerant{
 				Enumerant:    "RayQueryCandidateIntersectionAABBKHR",
 				Value:        1,
-				Capabilities: []string{"RayQueryKHR",},
+				Capabilities: []string{"RayQueryProvisionalKHR",},
 				Parameters:   []Parameter{},
 				Version:      "",
 			},
diff --git a/utils/vscode/src/tools/gen-grammar.go b/utils/vscode/src/tools/gen-grammar.go
index 200f695..a1289ef 100644
--- a/utils/vscode/src/tools/gen-grammar.go
+++ b/utils/vscode/src/tools/gen-grammar.go
@@ -31,7 +31,7 @@
 
 	"github.com/pkg/errors"
 
-	"../grammar"
+	"github.com/KhronosGroup/SPIRV-Tools/utils/vscode/src/grammar"
 )
 
 type grammarDefinition struct {
@@ -54,7 +54,7 @@
 			url:  "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.std.100.grammar.json",
 		}, {
 			name: "OpenCL.DebugInfo.100",
-			url:  "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Tools/master/source/extinst.opencl.debuginfo.100.grammar.json",
+			url:  "https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json",
 		},
 	}
 
@@ -106,7 +106,7 @@
 	for _, ext := range extensionGrammars {
 		root, err := parseGrammar(ext)
 		if err != nil {
-			return errors.Wrap(err, "Failed to parse extension grammar file")
+			return errors.Wrap(err, "Failed to parse extension grammar file: "+ext.name)
 		}
 		args.Extensions = append(args.Extensions, extension{Root: root, Name: ext.name})
 		args.All.Instructions = append(args.All.Instructions, root.Instructions...)