Squashed 'third_party/SPIRV-Tools/' changes from 8a5500656..82b378d67

82b378d67 spirv-opt: Add support to prevent functions from being inlined if they have DontInline flag (#3858)
56d0f5035 Propagate OpLine to all applied instructions in spirv-opt (#3951)
7403dfafd CMake: Add SPIRV_TOOLS_BUILD_STATIC flag (#3910)
25ee27576 Avoid copying a ref in a loop (#4000)
5edb328e8 spirv-val: Allow the ViewportIndex and Layer built-ins on SPIR-V 1.5 (#3986)
cbd1fa6c4 Simplify logic to decide whether CCP modified the IR (#3997)
df4198e50 Add DebugValue for DebugDecl invisible to value assignment (#3973)
34ae8a475 Fix bounds check instrumentation to handle 16-bit values (#3983)
abe2eff36 spirv-fuzz: Add expand vector reduction transformation (#3869)
99ad4f1e2 spirv-fuzz: Don't replace irrelevant indices in OpAccessChain (#3988)
895dafcc1 spirv-fuzz: Add FuzzerPassAddCompositeExtract (#3904)
69f07da41 spirv-fuzz: Fix mismatch with shrinker step limit (#3985)
9223493f4 spirv-fuzz: Fix off-by-one error in replayer (#3982)
4f5423187 spirv-fuzz: Get order right for OpSelect arguments (#3974)
88f7bcb6a spirv-fuzz: Do not add synonym-creating loops in dead blocks (#3975)
64eaa9832 spirv-fuzz: Skip OpTypeSampledImage when propagating up (#3976)
53f2a69c1 spirv-fuzz: Pass OpUndef in function call if needed (#3978)
de2c0ba20 spirv-fuzz: Fix off-by-one in TransformationCompositeConstruct (#3979)
dc4a18228 spirv-fuzz: Tolerate absent ids in data synonym fact management (#3966)
8496780f5 spirv-fuzz: Fix to id availability (#3971)
3b7aebca4 spirv-fuzz: Fix operand types (#3962)
6fe34cdf1 Update SPIRV-Headers revision in DEPS file (#3961)
a8d7062fe spirv-fuzz: Don't flatten conditional if condition is irrelevant (#3944)
a3d5378df spirv-fuzz: Do not produce OpPhis of type OpTypeSampledImage (#3964)
7edd0525b spirv-fuzz: Restrict fuzzer pass to reachable blocks (#3970)
f03779a1a spirv-fuzz: Handle more types when extending OpPhi instructions (#3969)
c794b7046 spirv-fuzz: Skip early terminator wrappers when merging returns (#3968)
dc9c6407d spirv-fuzz: Avoid irrelevant constants in synonym-creating loops (#3967)
26954c281 spirv-fuzz: Skip dead blocks in FuzzerPassAddOpPhiSynonyms (#3965)
5600fb85b spirv-fuzz: Avoid the type manager when looking for struct types (#3963)
ba15b5886 spirv-fuzz: Fix to TransformationDuplicateRegionWithSelection (#3941)
598435004 spirv-fuzz: Skip OpFunction when replacing irrelevant ids (#3932)
8362eae55 spirv-fuzz: Use component-wise selectors when flattening conditional branches (#3921)
ebe0ea09f Add SPV_EXT_shader_image_int64 (#3852)
a1d38174b Support SPV_KHR_fragment_shading_rate (#3943)
6fac705e7 spirv-val: Fix validation of OpPhi instructions (#3919)
b1350659b spirv-fuzz: Avoid void struct member when outlining functions (#3936)
fd0f295da spirv-fuzz: Do not allow Block-decorated structs when adding parameters (#3931)
5d7893b37 spirv-fuzz: Fix to operand id type (#3937)
cd1d3b6e0 spirv-fuzz: Handle dead blocks in TransformationEquationInstruction (#3933)
6cdae9da7 spirv-fuzz: Do not allow sampled image load when flattening conditionals (#3930)
7e1825a59 spirv-fuzz: Take care of OpPhi instructions when inlining (#3939)
502e98295 spirv-fuzz: Fix to TransformationInlineFunction (#3913)
bf1a11dab spirv-fuzz: Wrap early terminators before merging returns (#3925)
fd3948e16 Add DebugValue for function param regardless of scope (#3923)
663d050a9 Temporary fix to make GoogleTest compile. (#3922)
dd534e877 spirv-fuzz: Lower probability of adding bit instruction synonyms (#3917)
53aeba10c spirv-fuzz: Fix handling of OpPhi in FlattenConditionalBranch (#3916)
5c64374dd spirv-fuzz: Avoid creating blocks without parents (#3908)
57b3723c5 spirv-fuzz: Do not allow creation of constants of block-decorated structs (#3903)
12ca825a6 spirv-fuzz: Fixes related to irrelevant ids (#3901)
2e6cf706e spirv-fuzz: Fix to transformation that adds a synonym via a loop (#3898)
4b884928d spirv-fuzz: Fix to duplicate region with selection (#3896)
c2553a315 spirv-fuzz: Do not expose synonym facts for non-existent ids (#3891)
360228785 spirv-fuzz: Do not add synonyms involving irrelevant ids (#3890)
d52f79122 spirv-fuzz: Do not replace irrelevant ids that are not in blocks (#3892)
e02265992 spirv-fuzz: Wrap OpKill and similar in function calls (#3884)
11d592422 Update val to handle reversed instruction sections. (#3887)
fc8264854 spirv-fuzz: Replace dead-block terminators with OpKill etc. (#3882)
63cc22d64 spirv-fuzz: TransformationPropagateInstructionDown (#3692)
65b2a9e81 spirv-fuzz: Transformation to add wrappers for OpKill and similar (#3881)
624b16cd0 spirv-fuzz: Use overflow ids when duplicating regions (#3878)
bd0dd9cef spirv-fuzz: Fix rvalue references (#3883)
f15133788 spirv-fuzz: Avoid using block-decorated structs in transformations (#3877)
0e8553072 spirv-fuzz: Use overflow ids when inlining functions (#3880)
67f8e2edd Debug info preservation in convert-local-access-chains pass (#3835)
d91afd8de spirv-fuzz: Refactor fact manager tests (#3879)
b920b620a spirv-fuzz: Integrate spirv-reduce with shrinker (#3849)
74a711a76 spirv-fuzz: Only recommend passes when a pass had an effect (#3863)
fc7860e2d spirv-fuzz: Merge the return instructions in a function (#3838)
57abfd88c Debug info preservation in redundancy-elimination pass (#3839)
e24603836 Debug info preservation in if-conversion pass (#3861)
16cc197c8 spirv-fuzz: Refactor conditions in the fact manager (#3867)
615fbe6cb spirv-fuzz: TransformationWrapRegionInSelection (#3674)
f2b8a4ee5 spirv-fuzz: Enable some passes with high probability (#3860)
50e04f612 spirv-fuzz: Do not add constants for Block-decorated structs (#3862)
8edd79ddc spirv-fuzz: Fix to FuzzerPassFlattenConditionalBranches (#3865)
719bade4f spirv-fuzz: Fix TransformationRecordSynonymousConstants (#3868)
009facc97 spirv-fuzz: Fix flatten conditional branch transformation (#3859)
fcb22ecf0 spirv-fuzz: Report fresh ids in transformations (#3856)
c6ca885c0 [spirv-dis] Add some context comments to disassembly. (#3847)
446adb05f Fix use-after-move in val/validate.cpp (#3848)
4b07d50cd spirv-fuzz: Fix bug in TransformationDuplicateRegionWithSelection (#3819)
fec56146a spirv-fuzz: Support OpNot bit instruction case (#3841)
9e17b9d07 spirv-fuzz: Return IR and transformation context after replay (#3846)
e12087d6c spirv-fuzz: fix test (#3845)
9edeeafdb spirv-fuzz: Use unique_ptr<FactManager> in TransformationContext (#3844)
50dacda55 Start SPIRV-Tools v2020.6
b27e039c6 Finalize SPIRV-Tools v2020.5
a5903a969 Update CHANGES
330c72549 spirv-fuzz: Support dead blocks in TransformationAddSynonym (#3832)
36185f8b0 spirv-fuzz: Move IRContext parameter into constructor (#3837)
0e7fe4d35 Add missing backticks around <result-id> (#3840)
d1bb98fd4 Validate SPIRV Version number when parsing binary header (#3834)
67525bded spirv-fuzz: Create synonym of int constant using a loop (#3790)
7cc4b4d2c Fix compiler error on macOS with XCode12 (#3836)
5a5b750aa spirv-fuzz: Handle OpPhis in TransformationInlineFunction (#3833)
0a1fb588c Update CHANGES
125b64241 spirv-fuzz: Refactor fuzzer, replayer and shrinker (#3818)
60ce96e2f spirv-fuzz: Add pass recommendations (#3757)
2945963cc spirv-fuzz: Consider all ids from dead blocks irrelevant (#3795)
50ae4c5f4 Fix header guard macros (#3811)
296e9c7bc spirv-fuzz: Fix TransformationDuplicateRegionWithSelection (#3815)
937a757f0 spirv-val: Add DeviceIndex (#3812)
34ef0c3fd Fix missed modification flagging (#3814)
748edbf8c spirv-fuzz: Use an irrelevant id for the unused components (#3810)
8d49fb2f4 spirv-fuzz: Improvements to random number generation (#3809)
7e28d809c Add buffer oob check to bindless instrumentation (#3800)
8fc504110 spirv-fuzz: Remove CanFindOrCreateZeroConstant (#3807)
e8ce4355a spirv-fuzz: Add bit instruction synonym transformation (#3775)
e7c84feda spirv-fuzz: Skip unreachable blocks (#3729)
f20b523cb Fix build errors (#3804)
3131686d2 spirv-fuzz: Handle invalid ids in fact manager (#3742)
4c239bd81 spirv-fuzz: Support memory instructions MoveInstructionDown (#3700)
1e1c308de spirv-fuzz: Pass submanagers to other submanagers when necessary (#3796)
f62357e7b spirv-fuzz: Transformation to flatten conditional branch (#3667)
5df930054 spirv-val: Add BaseInstance, BaseVertex, DrawIndex, and ViewIndex (#3782)
286b3095d Properly mark IR changed if instruction folder creates more than one constant. (#3799)
726af6f78 Add missing file to BUILD.gn (#3798)
244e6c1be spirv-fuzz: Add TransformationDuplicateRegionWithSelection (#3773)
5dcb576b6 spirv-reduce: Support reducing a specific function (#3774)
de7d57984 spirv-reduce: Refactoring (#3793)
ed9863e46 Favour 'integrity' over 'coherence' as a replacement for 'sanity'. (#3619)
8743d385f spirv-fuzz: Fix header guards in transformations/fuzzer passes (#3784)
2de7d3af0 spirv-fuzz: Add SPIRV_FUZZ_PROTOC_COMMAND (#3789)
e589d0d57 Add missing include (#3788)
a715b1b40 Improve spirv-fuzz CMake code (#3781)
a187dd58a Allow SPV_KHR_8bit_storage extension. (#3780)
1ab52e54a spirv-opt: Add function to compute nesting depth of a block (#3771)
fd05605be spirv-fuzz: Transformation to convert OpSelect to conditional branch (#3681)
2c60d16a6 spirv-val: Add Vulkan VUID labels to BuiltIn (#3756)
c341f7a6c spirv-fuzz: Add support for BuiltIn decoration (#3736)
c278dada9 spirv-fuzz: Fix GetIdEquivalenceClasses (#3767)
788468408 spirv-fuzz: Replace id in OpPhi coming from a dead predecessor (#3744)
3daabd321 spirv-fuzz: Transformation to replace the use of an irrelevant id (#3697)
d7f078f27 spirv-fuzz: TransformationMutatePointer (#3737)
43a518601 spirv-fuzz: Compute interprocedural loop nesting depth of blocks (#3753)
8a0ebd40f Correctly replace debug lexical scope of instruction (#3718)
f428aa39c spirv-fuzz: Remove opaque pointer design pattern (#3755)
08291a3a9 spirv-fuzz: Create synonym via OpPhi and existing synonyms (#3701)
7e4948b2a Add LoopNestingDepth function to StructuredCFGAnalysis (#3754)
50cf38b8c spirv-fuzz: Do not make synonyms of void result ids (#3747)
bceab9fab Do not register DebugFunction for functions optimized away. (#3749)
e02f178a7 Handle DebugScope in compact-ids pass (#3724)
9e26ae045 spirv-fuzz: Overflow ids (#3734)
2205254cf Fix DebugNoScope to not output InlinedAt operand. (#3748)
230f363e6 spirv-fuzz: Split the fact manager into multiple files (#3699)
5adc5ae64 spirv-fuzz: Add inline function transformation (#3517)
1341b58a8 spirv-fuzz: Fix MaybeGetZeroConstant (#3740)
12df3cafe Fix SSA-rewrite to remove DebugDeclare for variables without loads (#3719)
3f8501de9 Add undef for inlined void function (#3720)
4dd122392 spirv-fuzz: Add words instead of logical operands (#3728)
b79773a35 CCP should mark IR changed if it created new constants. (#3732)
a711c594b spirv-fuzz: add FuzzerPassAddCompositeInserts (#3606)
582c276d4 spirv-fuzz: Support pointer types in FuzzerPassAddParameters (#3627)
3434cb0b0 Let ADCE pass check DebugScope (#3703)
ee7f0c882 spirv-opt: Implement opt::Function::HasEarlyReturn function (#3711)
e28436f2b spirv-fuzz: Check termination instructions when donating modules (#3710)
1023dd7a0 Fix -Wrange-loop-analysis warning (#3712)
82f4bf128 spirv-fuzz: Check header dominance when adding dead block (#3694)
b8de4f57e Allow DebugTypeTemplate for Type operand (#3702)
c20995ef8 spirv-fuzz: Improve code coverage of tests (#3686)
eade36db2 spirv-fuzz: Fuzzer pass to randomly apply loop preheaders (#3668)
72ea7bec4 spirv-fuzz: Support identical predecessors in TransformationPropagateInstructionUp (#3689)
b4c4da3e7 Improve non-semantic instruction handling in the optimizer (#3693)
948577c5d Fix the bug (#3680)
df859f77d spirv-fuzz: Check integer and float width capabilities (#3670)
2641d3351 spirv-fuzz: consider additional access chain instructions (#3672)
5e5929455 spirv-fuzz: Ignore specialization constants (#3664)
1435e427d Fix the bug (#3683)
be099cde1 spirv-fuzz: Fix width in FuzzerPassAddEquationInstructions (#3685)
f0ca96d12 Preserve debug info in dead-insert-elim pass (#3652)
0d629b903 Validate more OpenCL.DebugInfo.100 instructions (#3684)
13a65b1ae Only validation locations for appropriate execution models (#3656)
fd3cabd8b spirv-fuzz: Fix in operand type assertion (#3666)
f50553867 spirv-opt: Add spvOpcodeIsAccessChain (#3682)
b7056e7e0 spirv-fuzz: FuzzerPassPropagateInstructionsUp (#3478)
8e1380996 Handle no index access chain in local access chain convert (#3678)
bdeeae78a Roll 2 dependencies (#3677)
2990a2192 Avoid using /MP4 for clang on windows. (#3662)
7b2dd11dd spirv-fuzz: TransformationReplaceAddSubMulWithCarryingExtended (#3598)
6d7f34fbf spirv-fuzz: Add TransformationMakeVectorOperationDynamic (#3597)
d29eac95a spirv-fuzz: iterate over blocks in replace linear algebra pass (#3654)
efc85ff66 spirv-fuzz: make outliner pass use additional transformations (#3604)
5fd92a7e0 OpenCL.DebugInfo.100 DebugTypeArray with variable size (#3549)
3f33a9aa5 spirv-opt: Improve the code of the Instruction class (#3610)
0419751b0 spirv-fuzz: Handle OpPhis in livesafe functions (#3642)
a10e76059 spirv-fuzz: Handle OpPhi during constant obfuscation (#3640)
28f32ca53 spirv-fuzz: Fix FuzzerPassCopyObjects (#3638)
8bc27a1cf spirv-fuzz: Remove OpFunctionCall operands in correct order (#3630)
d9c73ebd9 spirv-fuzz: Handle capabilities during module donation (#3651)
9f2223602 spirv-fuzz: Refactor boilerplate in TransformationAddParameter (#3625)
92a71657f spirv-fuzz: TransformationMoveInstructionDown (#3477)
b78f4b151 Remove DebugDeclare only for target variables in ssa-rewrite (#3511)
91cea06ab Fix typo in ASAN CI build (#3623)
2aaa8653d spirv-fuzz: Transformation to add loop preheader (#3599)
96bcc8274 spirv-fuzz: Pass to replace int operands with ints of opposite signedness (#3612)
ebaefda66 Debug info preservation in loop-unroll pass (#3548)
50300450a Validator support for non-semantic clspv reflection (#3618)
ab4fe12a4 spirv-fuzz: Fix memory bugs (#3622)
c6e6597c4 spirv-fuzz: Implement the OpOuterProduct linear algebra case (#3617)
054f034ea spirv-fuzz: Compute corollary facts from OpBitcast (#3538)
a1ea15c90 Update some language usage. (#3611)
863b8e3d3 spirv-fuzz: Relax type constraints in DataSynonym facts (#3602)
7e75fea9e spirv-fuzz: Remove non-deterministic behaviour (#3608)
f9b088fe0 Avoid use of 'sanity' and 'sanity check' in the code base (#3585)
150be20d4 spirv-fuzz: Add condition to make functions livesafe (#3587)
ce16ccf38 Rolling 4 dependencies (#3601)
1dfc6fc7e spirv-fuzz: Implement the OpTranspose linear algebra case (#3589)
b63f0e5ed Fix SyntaxWarning in Python 3.8 (#3388)
6aed7ffbc CMake: Enable building with BUILD_SHARED_LIBS=1 (#3490)
31c821393 Avoid operand type range checks (#3379)
6a3eb679b Preserve debug info in scalar replacement pass (#3461)
2796840d2 Update OpenCL capabilities validation (#3149)
b25ee93c2 build(deps): bump lodash from 4.17.15 to 4.17.19 in /tools/sva (#3596)

git-subtree-dir: third_party/SPIRV-Tools
git-subtree-split: 82b378d671836b51343b010ca9ec32db14485147
diff --git a/.appveyor.yml b/.appveyor.yml
index 77cd89c..0a4cca0 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -46,7 +46,8 @@
 
 before_build:
   - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers
-  - git clone --depth=1 https://github.com/google/googletest.git external/googletest
+  - 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
diff --git a/Android.mk b/Android.mk
index 5c495cd..0b64ea6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -149,7 +149,6 @@
 		source/opt/pass.cpp \
 		source/opt/pass_manager.cpp \
 		source/opt/private_to_local_pass.cpp \
-		source/opt/process_lines_pass.cpp \
 		source/opt/propagator.cpp \
 		source/opt/reduce_load_size.cpp \
 		source/opt/redundancy_elimination.cpp \
@@ -181,11 +180,10 @@
 # Locations of grammar files.
 #
 SPV_COREUNIFIED1_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/spirv.core.grammar.json
-SPV_GLSL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/extinst.glsl.std.450.grammar.json
-SPV_OPENCL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/extinst.opencl.std.100.grammar.json
-# TODO(dneto): I expect the DebugInfo grammar file to eventually migrate to SPIRV-Headers
-SPV_DEBUGINFO_GRAMMAR=$(LOCAL_PATH)/source/extinst.debuginfo.grammar.json
-SPV_CLDEBUGINFO100_GRAMMAR=$(LOCAL_PATH)/source/extinst.opencl.debuginfo.100.grammar.json
+SPV_GLSL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.glsl.std.450.grammar.json
+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
 
 define gen_spvtools_grammar_tables
 $(call generate-file-dir,$(1)/core.insts-unified1.inc)
@@ -252,9 +250,9 @@
 $(call generate-file-dir,$(1)/$(2).insts.inc)
 $(1)/$(2).insts.inc : \
         $(LOCAL_PATH)/utils/generate_grammar_tables.py \
-        $(LOCAL_PATH)/source/extinst.$(2).grammar.json
+        $(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.$(2).grammar.json
 		@$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \
-		    --extinst-vendor-grammar=$(LOCAL_PATH)/source/extinst.$(2).grammar.json \
+		    --extinst-vendor-grammar=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.$(2).grammar.json \
 		    --vendor-insts-output=$(1)/$(2).insts.inc \
 		    --vendor-operand-kind-prefix=$(3)
 		@echo "[$(TARGET_ARCH_ABI)] Vendor extended instruction set: $(2) tables <= grammar"
@@ -267,6 +265,7 @@
 $(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,""))
 $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-trinary-minmax,""))
+$(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),nonsemantic.clspvreflection,""))
 
 define gen_spvtools_enum_string_mapping
 $(call generate-file-dir,$(1)/extension_enum.inc.inc)
diff --git a/BUILD.bazel b/BUILD.bazel
index 3046781..52290cf 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -59,6 +59,8 @@
 
 generate_vendor_tables("opencl.debuginfo.100", "CLDEBUG100_")
 
+generate_vendor_tables("nonsemantic.clspvreflection")
+
 generate_extinst_lang_headers("DebugInfo", DEBUGINFO_GRAMMAR_JSON_FILE)
 
 generate_extinst_lang_headers("OpenCLDebugInfo100", CLDEBUGINFO100_GRAMMAR_JSON_FILE)
@@ -103,6 +105,7 @@
         ":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_spv_amd_gcn_shader",
         ":gen_vendor_tables_spv_amd_shader_ballot",
diff --git a/BUILD.gn b/BUILD.gn
index fae7957..55eb9ad 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -32,8 +32,8 @@
         "${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 = "source/extinst.debuginfo.grammar.json"
-    cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.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 = [
       core_json_file,
@@ -69,8 +69,8 @@
 
     core_json_file =
         "${spirv_headers}/include/spirv/$version/spirv.core.grammar.json"
-    debuginfo_insts_file = "source/extinst.debuginfo.grammar.json"
-    cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.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"
     extension_map_file = "${target_gen_dir}/enum_string_mapping.inc"
@@ -110,8 +110,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 = "source/extinst.debuginfo.grammar.json"
-    cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.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"
 
@@ -150,8 +150,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 = "source/extinst.debuginfo.grammar.json"
-    cldebuginfo100_insts_file = "source/extinst.opencl.debuginfo.100.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"
 
@@ -210,7 +210,7 @@
     script = "utils/generate_grammar_tables.py"
 
     name = invoker.name
-    extinst_vendor_grammar = "source/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 = [
@@ -280,11 +280,11 @@
 }
 spvtools_language_header("debuginfo") {
   name = "DebugInfo"
-  grammar_file = "source/extinst.debuginfo.grammar.json"
+  grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.debuginfo.grammar.json"
 }
 spvtools_language_header("cldebuginfo100") {
   name = "OpenCLDebugInfo100"
-  grammar_file = "source/extinst.opencl.debuginfo.100.grammar.json"
+  grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json"
 }
 
 spvtools_vendor_tables = [
@@ -294,6 +294,7 @@
   ["spv-amd-shader-ballot", "...nil..."],
   ["debuginfo", "...nil..."],
   ["opencl.debuginfo.100", "CLDEBUG100_"],
+  ["nonsemantic.clspvreflection", "...nil..."],
 ]
 
 foreach(table_def, spvtools_vendor_tables) {
@@ -653,8 +654,6 @@
     "source/opt/passes.h",
     "source/opt/private_to_local_pass.cpp",
     "source/opt/private_to_local_pass.h",
-    "source/opt/process_lines_pass.cpp",
-    "source/opt/process_lines_pass.h",
     "source/opt/propagator.cpp",
     "source/opt/propagator.h",
     "source/opt/reduce_load_size.cpp",
@@ -773,6 +772,7 @@
     "source/reduce/reducer.h",
     "source/reduce/reduction_opportunity.cpp",
     "source/reduce/reduction_opportunity.h",
+    "source/reduce/reduction_opportunity_finder.cpp",
     "source/reduce/reduction_opportunity_finder.h",
     "source/reduce/reduction_pass.cpp",
     "source/reduce/reduction_pass.h",
diff --git a/CHANGES b/CHANGES
index d10f55b..3c81249 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,7 +1,56 @@
 Revision history for SPIRV-Tools
 
-v2020.5 2020-07-22
- - Start SPIRV-Tools v2020.5
+v2020.6 2020-09-24
+ - Start SPIRV-Tools v2020.6
+
+v2020.5 2020-09-22
+ - General
+   - Enable building with BUILD_SHARED_LIBS=1 (#3490)
+   - Avoid using /MP4 for clang on windows. (#3662)
+   - Fix compiler error on macOS with XCode12. (#3836)
+ - Optimizer
+   - Preserve OpenCL.DebugInfo.100 through private-to-local pass (#3571)
+   - Preserve debug info in scalar replacement pass (#3461)
+   - Debug info preservation in loop-unroll pass (#3548)
+   - Preserve debug info in dead-insert-elim pass (#3652)
+   - Improve non-semantic instruction handling in the optimizer (#3693)
+   - Let ADCE pass check DebugScope (#3703)
+   - Add undef for inlined void function (#3720)
+   - Fix SSA-rewrite to remove DebugDeclare for variables without loads (#3719)
+   - Handle DebugScope in compact-ids pass (#3724)
+   - Add buffer oob check to bindless instrumentation (#3800)
+ - Validator
+   - Update OpenCL capabilities validation (#3149)
+   - Validator support for non-semantic clspv reflection (#3618)
+   - OpenCL.DebugInfo.100 DebugTypeArray with variable size (#3549)
+   - Only validation locations for appropriate execution models (#3656)
+   - Validate more OpenCL.DebugInfo.100 instructions (#3684)
+   - Allow DebugTypeTemplate for Type operand (#3702)
+   - spirv-val: Add Vulkan VUID labels to BuiltIn (#3756)
+   - Allow SPV_KHR_8bit_storage extension. (#3780)
+   - Validate SPIRV Version number when parsing binary header (#3834)
+ - Reduce
+   - Support reducing a specific function (#3774)
+ - Fuzz
+   - adds TransformationReplaceCopyObjectWithStoreLoad (#3567)
+   - adds TransformationReplaceCopyMemoryWithLoadStore (#3575)
+   - adds TransformationReplaceLoadStoreWithCopyMemory (#3586)
+   - Implement the OpOuterProduct linear algebra case (#3617)
+   - Pass to replace int operands with ints of opposite signedness (#3612)
+   - TransformationMoveInstructionDown (#3477)
+   - Add TransformationMakeVectorOperationDynamic (#3597)
+   - TransformationReplaceAddSubMulWithCarryingExtended (#3598)
+   - FuzzerPassPropagateInstructionsUp (#3478)
+   - add FuzzerPassAddCompositeInserts (#3606)
+   - Add inline function transformation (#3517)
+   - Transformation to replace the use of an irrelevant id (#3697)
+   - Add SPIRV_FUZZ_PROTOC_COMMAND (#3789)
+   - Add TransformationDuplicateRegionWithSelection (#3773)
+   - Transformation to flatten conditional branch (#3667)
+   - Handle OpPhis in TransformationInlineFunction (#3833)
+   - Create synonym of int constant using a loop (#3790)
+   - Support dead blocks in TransformationAddSynonym (#3832)
+ - Linker
 
 v2020.4 2020-07-22
  - General
@@ -39,7 +88,7 @@
 
 v2020.3 2020-05-27
  - General
-   - Prevent Effcee install his things when build spirv-tools with testing enabled (#3256)
+   - Prevent Effcee from installing things when building spirv-tools with testing enabled (#3256)
    - Update acorn version (#3294)
    - If SPIRV-Headers is in our tree, include it as subproject (#3299)
    - allow cross compiling for Windows Store, UWP, etc. (#3330)
@@ -111,7 +160,7 @@
  - Optimizer
    - Change default version for CreatInstBindlessCheckPass to 2 (#3096, #3119)
    - Better handling of OpLine on merge blocks (#3130)
-   - Use dummy switch instead of dummy loop in MergeReturn pass. (#3151)
+   - Use placeholder switch instead of placeholder loop in MergeReturn pass. (#3151)
    - Handle TimeAMD in AmdExtensionToKhrPass. (#3168)
  - Validator
    - Fix structured exit validation (#3141)
@@ -438,7 +487,7 @@
  - Optimizer
    - Unrolling loops marked for unrolling in the legalization passes.
    - Improved the compile time of loop unrolling.
-   - Changee merge-return to create a dummy loop around the function.
+   - Changee merge-return to create a placeholder loop around the function.
    - Small improvement to merge-blocks to allow it to merge more often.
    - Enforce an upper bound for the ids, and add option to set it.
    - #1966: Report error if there are unreachable block before running merge return
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 73248f2..55f84e6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -79,6 +79,8 @@
           "Please remove SPIRV_BUILD_COMPRESSION from your build options.")
 endif(SPIRV_BUILD_COMPRESSION)
 
+option(SPIRV_BUILD_FUZZER "Build spirv-fuzz" 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)
@@ -130,6 +132,46 @@
   add_definitions(${SPIRV_TOOLS_EXTRA_DEFINITIONS})
 endif()
 
+# Library build setting definitions:
+#
+# * SPIRV_TOOLS_BUILD_STATIC - ON or OFF - Defaults to ON.
+#   If enabled the following targets will be created:
+#     ${SPIRV_TOOLS}-static - STATIC library.
+#                             Has full public symbol visibility.
+#     ${SPIRV_TOOLS}-shared - SHARED library.
+#                             Has default-hidden symbol visibility.
+#     ${SPIRV_TOOLS}        - will alias to one of above, based on BUILD_SHARED_LIBS.
+#   If disabled the following targets will be created:
+#     ${SPIRV_TOOLS}        - either STATIC or SHARED based on SPIRV_TOOLS_LIBRARY_TYPE.
+#                             Has full public symbol visibility.
+#     ${SPIRV_TOOLS}-shared - SHARED library.
+#                             Has default-hidden symbol visibility.
+#
+# * SPIRV_TOOLS_LIBRARY_TYPE - SHARED or STATIC.
+#   Specifies the library type used for building SPIRV-Tools libraries.
+#   Defaults to SHARED when BUILD_SHARED_LIBS=1, otherwise STATIC.
+#
+# * SPIRV_TOOLS_FULL_VISIBILITY - "${SPIRV_TOOLS}-static" or "${SPIRV_TOOLS}"
+#   Evaluates to the SPIRV_TOOLS target library name that has no hidden symbols.
+#   This is used by internal targets for accessing symbols that are non-public.
+#   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(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)
+  set(SPIRV_TOOLS_LIBRARY_TYPE "STATIC")
+else(SPIRV_TOOLS_BUILD_STATIC)
+  set(SPIRV_TOOLS_FULL_VISIBILITY ${SPIRV_TOOLS})
+  if (NOT DEFINED SPIRV_TOOLS_LIBRARY_TYPE)
+      if(BUILD_SHARED_LIBS)
+        set(SPIRV_TOOLS_LIBRARY_TYPE "SHARED")
+      else()
+        set(SPIRV_TOOLS_LIBRARY_TYPE "STATIC")
+      endif()
+  endif()
+endif(SPIRV_TOOLS_BUILD_STATIC)
+
 function(spvtools_default_compile_options TARGET)
   target_compile_options(${TARGET} PRIVATE ${SPIRV_WARNINGS})
 
diff --git a/DEPS b/DEPS
index a27b921..ef3ee5d 100644
--- a/DEPS
+++ b/DEPS
@@ -3,10 +3,10 @@
 vars = {
   'github': 'https://github.com',
 
-  'effcee_revision': '5af957bbfc7da4e9f7aa8cac11379fa36dd79b84',
-  'googletest_revision': '011959aafddcd30611003de96cfd8d7a7685c700',
-  're2_revision': 'aecba11114cf1fac5497aeb844b6966106de3eb6',
-  'spirv_headers_revision': 'ac638f1815425403e946d0ab78bac71d2bdbf3be',
+  'effcee_revision': '2ec8f8738118cc483b67c04a759fee53496c5659',
+  'googletest_revision': '3af06fe1664d30f98de1e78c53a7087e842a2547',
+  're2_revision': 'ca11026a032ce2a3de4b3c389ee53d2bdc8794d6',
+  'spirv_headers_revision': '05836bdba63e7debce9fa9feaed42f20cd43af9d',
 }
 
 deps = {
diff --git a/README.md b/README.md
index c82ca19..44f582f 100644
--- a/README.md
+++ b/README.md
@@ -349,10 +349,7 @@
 
 ```sh
 # In <spirv-dir> (the SPIRV-Tools repo root):
-git clone https://github.com/protocolbuffers/protobuf external/protobuf
-pushd external/protobuf
-git checkout v3.7.1
-popd
+git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
 
 # In your build directory:
 cmake [-G <platform-generator>] <spirv-dir> -DSPIRV_BUILD_FUZZER=ON
diff --git a/build_defs.bzl b/build_defs.bzl
index 15b70c7..30af3bd 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -39,8 +39,8 @@
     ],
 })
 
-DEBUGINFO_GRAMMAR_JSON_FILE = "source/extinst.debuginfo.grammar.json"
-CLDEBUGINFO100_GRAMMAR_JSON_FILE = "source/extinst.opencl.debuginfo.100.grammar.json"
+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"
 
 def generate_core_tables(version = None):
     if not version:
@@ -146,7 +146,7 @@
     if not extension:
         fail("Must specify extension", "extension")
     extension_rule = extension.replace("-", "_").replace(".", "_")
-    grammars = ["source/extinst.{}.grammar.json".format(extension)]
+    grammars = ["@spirv_headers//:spirv_ext_inst_{}_grammar_unified1".format(extension_rule)]
     outs = ["{}.insts.inc".format(extension)]
     prefices = [operand_kind_prefix]
     fmtargs = grammars + outs + prefices
diff --git a/docs/syntax.md b/docs/syntax.md
index be3a5d5..c135d01 100644
--- a/docs/syntax.md
+++ b/docs/syntax.md
@@ -166,9 +166,9 @@
 
 When a token in the assembly program is a `!<integer>`, that integer value is
 emitted into the binary output, and parsing proceeds differently than before:
-each subsequent token not recognized as an OpCode or a <result-id> is emitted
+each subsequent token not recognized as an OpCode or a `<result-id>` is emitted
 into the binary output without any checking; when a recognizable OpCode or a
-<result-id> is eventually encountered, it begins a new instruction and parsing
+`<result-id>` is eventually encountered, it begins a new instruction and parsing
 returns to normal.  (If a subsequent OpCode is never found, then this alternate
 parsing mode handles all the remaining tokens in the program.)
 
diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt
index 5b34159..179a401 100644
--- a/external/CMakeLists.txt
+++ b/external/CMakeLists.txt
@@ -13,6 +13,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Utility functions for pushing & popping variables.
+function(push_variable var val)
+    set("${var}_SAVE_STACK" "${${var}}" "${${var}_SAVE_STACK}" PARENT_SCOPE)
+    set(${var} ${val} PARENT_SCOPE)
+endfunction()
+function(pop_variable var)
+    set(save_stack "${${var}_SAVE_STACK}")
+    list(GET save_stack 0 val)
+    list(REMOVE_AT save_stack 0)
+    set("${var}_SAVE_STACK" "${save_stack}" PARENT_SCOPE)
+    set(${var} ${val} PARENT_SCOPE)
+endfunction()
+
 if (DEFINED SPIRV-Headers_SOURCE_DIR)
   # This allows flexible position of the SPIRV-Headers repo.
   set(SPIRV_HEADER_DIR ${SPIRV-Headers_SOURCE_DIR})
@@ -61,7 +74,11 @@
           "Use shared (DLL) run-time lib even when Google Test is built as static lib."
           ON)
       endif()
+      # gtest requires special defines for building as a shared
+      # library, simply always build as static.
+      push_variable(BUILD_SHARED_LIBS 0)
       add_subdirectory(${GMOCK_DIR} EXCLUDE_FROM_ALL)
+      pop_variable(BUILD_SHARED_LIBS)
     endif()
   endif()
   if (TARGET gmock)
@@ -108,7 +125,9 @@
       if (NOT TARGET effcee)
         set(EFFCEE_BUILD_TESTING OFF CACHE BOOL "Do not build Effcee test suite")
       endif()
+      push_variable(BUILD_SHARED_LIBS 0) # effcee does not export any symbols for building as a DLL. Always build as static.
       add_subdirectory(effcee EXCLUDE_FROM_ALL)
+      pop_variable(BUILD_SHARED_LIBS)
       set_property(TARGET effcee PROPERTY FOLDER Effcee)
       # Turn off warnings for effcee and re2
       set_property(TARGET effcee APPEND PROPERTY COMPILE_OPTIONS -w)
@@ -118,16 +137,43 @@
 endif()
 
 if(SPIRV_BUILD_FUZZER)
-  set(PROTOBUF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/cmake)
-  set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests")
-  set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime")
-  if (IS_DIRECTORY ${PROTOBUF_DIR})
-    if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
-      add_definitions(-Wno-inconsistent-missing-override)
+
+  function(backup_compile_options)
+    get_property(
+        SPIRV_TOOLS_BACKUP_EXTERNAL_COMPILE_OPTIONS
+        DIRECTORY
+        PROPERTY COMPILE_OPTIONS
+        )
+  endfunction()
+
+  function(restore_compile_options)
+    set_property(
+        DIRECTORY
+        PROPERTY COMPILE_OPTIONS
+        ${SPIRV_TOOLS_BACKUP_EXTERNAL_COMPILE_OPTIONS}
+    )
+  endfunction()
+
+  if(NOT TARGET protobuf::libprotobuf OR NOT TARGET protobuf::protoc)
+
+    set(SPIRV_TOOLS_PROTOBUF_DIR ${CMAKE_CURRENT_SOURCE_DIR}/protobuf/cmake)
+    if (NOT IS_DIRECTORY ${SPIRV_TOOLS_PROTOBUF_DIR})
+      message(
+          FATAL_ERROR
+          "protobuf not found - please checkout a copy under external/.")
     endif()
-    add_subdirectory(${PROTOBUF_DIR} EXCLUDE_FROM_ALL)
-  else()
-    message(FATAL_ERROR
-      "protobuf not found - please checkout a copy under external/.")
+    set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests")
+    set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime")
+
+    backup_compile_options()
+
+    if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
+      add_compile_options(-Wno-inconsistent-missing-override)
+    endif()
+
+    add_subdirectory(${SPIRV_TOOLS_PROTOBUF_DIR} EXCLUDE_FROM_ALL)
+
+    restore_compile_options()
+
   endif()
-endif(SPIRV_BUILD_FUZZER)
+endif()
diff --git a/include/spirv-tools/instrument.hpp b/include/spirv-tools/instrument.hpp
index b4f3355..9c01cb6 100644
--- a/include/spirv-tools/instrument.hpp
+++ b/include/spirv-tools/instrument.hpp
@@ -24,6 +24,7 @@
 //
 //   CreateInstBindlessCheckPass
 //   CreateInstBuffAddrCheckPass
+//   CreateInstDebugPrintfPass
 //
 // More detailed documentation of these routines can be found in optimizer.hpp
 
@@ -33,7 +34,7 @@
 //
 // The following values provide offsets into the output buffer struct
 // generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
-// by InstBindlessCheckPass.
+// by InstBindlessCheckPass, InstBuffAddrCheckPass, and InstDebugPrintfPass.
 //
 // The first member of the debug output buffer contains the next available word
 // in the data stream to be written. Shaders will atomically read and update
@@ -138,12 +139,21 @@
 // A bindless bounds error will output the index and the bound.
 static const int kInstBindlessBoundsOutDescIndex = kInstStageOutCnt + 1;
 static const int kInstBindlessBoundsOutDescBound = kInstStageOutCnt + 2;
-static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 3;
+static const int kInstBindlessBoundsOutUnused = kInstStageOutCnt + 3;
+static const int kInstBindlessBoundsOutCnt = kInstStageOutCnt + 4;
 
-// A bindless uninitialized error will output the index.
+// A descriptor uninitialized error will output the index.
 static const int kInstBindlessUninitOutDescIndex = kInstStageOutCnt + 1;
 static const int kInstBindlessUninitOutUnused = kInstStageOutCnt + 2;
-static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 3;
+static const int kInstBindlessUninitOutUnused2 = kInstStageOutCnt + 3;
+static const int kInstBindlessUninitOutCnt = kInstStageOutCnt + 4;
+
+// A buffer out-of-bounds error will output the descriptor
+// index, the buffer offset and the buffer size
+static const int kInstBindlessBuffOOBOutDescIndex = kInstStageOutCnt + 1;
+static const int kInstBindlessBuffOOBOutBuffOff = kInstStageOutCnt + 2;
+static const int kInstBindlessBuffOOBOutBuffSize = kInstStageOutCnt + 3;
+static const int kInstBindlessBuffOOBOutCnt = kInstStageOutCnt + 4;
 
 // A buffer address unalloc error will output the 64-bit pointer in
 // two 32-bit pieces, lower bits first.
@@ -152,7 +162,7 @@
 static const int kInstBuffAddrUnallocOutCnt = kInstStageOutCnt + 3;
 
 // Maximum Output Record Member Count
-static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
+static const int kInstMaxOutCnt = kInstStageOutCnt + 4;
 
 // Validation Error Codes
 //
@@ -160,6 +170,7 @@
 static const int kInstErrorBindlessBounds = 0;
 static const int kInstErrorBindlessUninit = 1;
 static const int kInstErrorBuffAddrUnallocRef = 2;
+static const int kInstErrorBindlessBuffOOB = 3;
 
 // Direct Input Buffer Offsets
 //
@@ -197,7 +208,10 @@
 // At offset kDebugInputBindlessInitOffset in Data[] is a single uint which
 // gives an offset to the start of the bindless initialization data. More
 // specifically, if the following value is zero, we know that the descriptor at
-// (set = s, binding = b, index = i) is not initialized:
+// (set = s, binding = b, index = i) is not initialized; if the value is
+// non-zero, and the descriptor points to a buffer, the value is the length of
+// the buffer in bytes and can be used to check for out-of-bounds buffer
+// references:
 // Data[ i + Data[ b + Data[ s + Data[ kDebugInputBindlessInitOffset ] ] ] ]
 static const int kDebugInputBindlessInitOffset = 0;
 
diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h
index 68afd64..a0114c3 100644
--- a/include/spirv-tools/libspirv.h
+++ b/include/spirv-tools/libspirv.h
@@ -48,8 +48,8 @@
 
 #define SPV_BIT(shift) (1 << (shift))
 
-#define SPV_FORCE_16_BIT_ENUM(name) _##name = 0x7fff
-#define SPV_FORCE_32_BIT_ENUM(name) _##name = 0x7fffffff
+#define SPV_FORCE_16_BIT_ENUM(name) SPV_FORCE_16BIT_##name = 0x7fff
+#define SPV_FORCE_32_BIT_ENUM(name) SPV_FORCE_32BIT_##name = 0x7fffffff
 
 // Enumerations
 
@@ -176,12 +176,13 @@
 
   // Set 5:  Operands that are a single word bitmask.
   // Sometimes a set bit indicates the instruction requires still more operands.
-  SPV_OPERAND_TYPE_IMAGE,              // SPIR-V Sec 3.14
-  SPV_OPERAND_TYPE_FP_FAST_MATH_MODE,  // SPIR-V Sec 3.15
-  SPV_OPERAND_TYPE_SELECTION_CONTROL,  // SPIR-V Sec 3.22
-  SPV_OPERAND_TYPE_LOOP_CONTROL,       // SPIR-V Sec 3.23
-  SPV_OPERAND_TYPE_FUNCTION_CONTROL,   // SPIR-V Sec 3.24
-  SPV_OPERAND_TYPE_MEMORY_ACCESS,      // SPIR-V Sec 3.26
+  SPV_OPERAND_TYPE_IMAGE,                  // SPIR-V Sec 3.14
+  SPV_OPERAND_TYPE_FP_FAST_MATH_MODE,      // SPIR-V Sec 3.15
+  SPV_OPERAND_TYPE_SELECTION_CONTROL,      // SPIR-V Sec 3.22
+  SPV_OPERAND_TYPE_LOOP_CONTROL,           // SPIR-V Sec 3.23
+  SPV_OPERAND_TYPE_FUNCTION_CONTROL,       // SPIR-V Sec 3.24
+  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.
 // There are two categories:
@@ -189,8 +190,17 @@
 //    Variable : expands to 0, 1 or many operands or pairs of operands.
 //               This is similar to * in regular expressions.
 
+// NOTE: These FIRST_* and LAST_* enum values are DEPRECATED.
+// The concept of "optional" and "variable" operand types are only intended
+// for use as an implementation detail of parsing SPIR-V, either in text or
+// binary form.  Instead of using enum ranges, use characteristic function
+// spvOperandIsConcrete.
+// The use of enum value ranges in a public API makes it difficult to insert
+// new values into a range without also breaking binary compatibility.
+//
 // Macros for defining bounds on optional and variable operand types.
 // Any variable operand type is also optional.
+// TODO(dneto): Remove SPV_OPERAND_TYPE_FIRST_* and SPV_OPERAND_TYPE_LAST_*
 #define FIRST_OPTIONAL(ENUM) ENUM, SPV_OPERAND_TYPE_FIRST_OPTIONAL_TYPE = ENUM
 #define FIRST_VARIABLE(ENUM) ENUM, SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE = ENUM
 #define LAST_VARIABLE(ENUM)                         \
@@ -257,6 +267,12 @@
   SPV_FORCE_32_BIT_ENUM(spv_operand_type_t)
 } spv_operand_type_t;
 
+// Returns true if the given type is concrete.
+bool spvOperandIsConcrete(spv_operand_type_t type);
+
+// Returns true if the given type is concrete and also a mask.
+bool spvOperandIsConcreteMask(spv_operand_type_t type);
+
 typedef enum spv_ext_inst_type_t {
   SPV_EXT_INST_TYPE_NONE = 0,
   SPV_EXT_INST_TYPE_GLSL_STD_450,
@@ -267,6 +283,7 @@
   SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT,
   SPV_EXT_INST_TYPE_DEBUGINFO,
   SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100,
+  SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION,
 
   // Multiple distinct extended instruction set types could return this
   // value, if they are prefixed with NonSemantic. and are otherwise
@@ -309,6 +326,8 @@
   // time, but will use common names for scalar types, and debug names from
   // OpName instructions.
   SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES = SPV_BIT(6),
+  // Add some comments to the generated assembly
+  SPV_BINARY_TO_TEXT_OPTION_COMMENT = SPV_BIT(7),
   SPV_FORCE_32_BIT_ENUM(spv_binary_to_text_options_t)
 } spv_binary_to_text_options_t;
 
@@ -658,6 +677,13 @@
 SPIRV_TOOLS_EXPORT void spvReducerOptionsSetFailOnValidationError(
     spv_reducer_options options, bool fail_on_validation_error);
 
+// Sets the function that the reducer should target.  If set to zero the reducer
+// will target all functions as well as parts of the module that lie outside
+// functions.  Otherwise the reducer will restrict reduction to the function
+// with result id |target_function|, which is required to exist.
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction(
+    spv_reducer_options options, uint32_t target_function);
+
 // Creates a fuzzer options object with default options. Returns a valid
 // options object. The object remains valid until it is passed into
 // |spvFuzzerOptionsDestroy|.
@@ -692,6 +718,11 @@
 SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableFuzzerPassValidation(
     spv_fuzzer_options options);
 
+// Enables all fuzzer passes during a fuzzing run (instead of a random subset
+// of passes).
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses(
+    spv_fuzzer_options options);
+
 // Encodes the given SPIR-V assembly text to its binary representation. The
 // length parameter specifies the number of bytes for text. Encoded binary will
 // be stored into *binary. Any error will be written into *diagnostic if
diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp
index 6b31a07..2018e46 100644
--- a/include/spirv-tools/libspirv.hpp
+++ b/include/spirv-tools/libspirv.hpp
@@ -202,6 +202,11 @@
                                               fail_on_validation_error);
   }
 
+  // See spvReducerOptionsSetTargetFunction.
+  void set_target_function(uint32_t target_function) {
+    spvReducerOptionsSetTargetFunction(options_, target_function);
+  }
+
  private:
   spv_reducer_options options_;
 };
@@ -242,6 +247,9 @@
     spvFuzzerOptionsEnableFuzzerPassValidation(options_);
   }
 
+  // See spvFuzzerOptionsEnableAllPasses.
+  void enable_all_passes() { spvFuzzerOptionsEnableAllPasses(options_); }
+
  private:
   spv_fuzzer_options options_;
 };
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 741f947..7f993cc 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -536,30 +536,6 @@
 // eliminated with standard dead code elimination.
 Optimizer::PassToken CreateAggressiveDCEPass();
 
-// Create line propagation pass
-// This pass propagates line information based on the rules for OpLine and
-// OpNoline and clones an appropriate line instruction into every instruction
-// which does not already have debug line instructions.
-//
-// This pass is intended to maximize preservation of source line information
-// through passes which delete, move and clone instructions. Ideally it should
-// be run before any such pass. It is a bookend pass with EliminateDeadLines
-// which can be used to remove redundant line instructions at the end of a
-// run of such passes and reduce final output file size.
-Optimizer::PassToken CreatePropagateLineInfoPass();
-
-// Create dead line elimination pass
-// This pass eliminates redundant line instructions based on the rules for
-// OpLine and OpNoline. Its main purpose is to reduce the size of the file
-// need to store the SPIR-V without losing line information.
-//
-// This is a bookend pass with PropagateLines which attaches line instructions
-// to every instruction to preserve line information during passes which
-// delete, move and clone instructions. DeadLineElim should be run after
-// PropagateLines and all such subsequent passes. Normally it would be one
-// of the last passes to be run.
-Optimizer::PassToken CreateRedundantLineInfoElimPass();
-
 // Creates a compact ids pass.
 // The pass remaps result ids to a compact and gapless range starting from %1.
 Optimizer::PassToken CreateCompactIdsPass();
@@ -764,7 +740,7 @@
 // initialization checking, both of which require input buffer support.
 Optimizer::PassToken CreateInstBindlessCheckPass(
     uint32_t desc_set, uint32_t shader_id, bool input_length_enable = false,
-    bool input_init_enable = false);
+    bool input_init_enable = false, bool input_buff_oob_enable = false);
 
 // Create a pass to instrument physical buffer address checking
 // This pass instruments all physical buffer address references to check that
diff --git a/kokoro/android/build.sh b/kokoro/android/build.sh
index c5a0a87..c05c139 100644
--- a/kokoro/android/build.sh
+++ b/kokoro/android/build.sh
@@ -35,7 +35,8 @@
 
 cd $SRC
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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
 
diff --git a/kokoro/check-format/build.sh b/kokoro/check-format/build.sh
index 2a8d50f..0c4b8d1 100644
--- a/kokoro/check-format/build.sh
+++ b/kokoro/check-format/build.sh
@@ -31,7 +31,8 @@
 
 cd $SRC
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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;
diff --git a/kokoro/linux-clang-release-bazel/build.sh b/kokoro/linux-clang-release-bazel/build.sh
index cc38bd4..05a9bbb 100644
--- a/kokoro/linux-clang-release-bazel/build.sh
+++ b/kokoro/linux-clang-release-bazel/build.sh
@@ -26,7 +26,8 @@
 
 cd $SRC
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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
 
diff --git a/kokoro/macos-clang-release-bazel/build.sh b/kokoro/macos-clang-release-bazel/build.sh
index e92fa74..d2a516f 100644
--- a/kokoro/macos-clang-release-bazel/build.sh
+++ b/kokoro/macos-clang-release-bazel/build.sh
@@ -26,7 +26,8 @@
 
 cd $SRC
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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
 
diff --git a/kokoro/ndk-build/build.sh b/kokoro/ndk-build/build.sh
index d51f071..f1f167d 100644
--- a/kokoro/ndk-build/build.sh
+++ b/kokoro/ndk-build/build.sh
@@ -34,7 +34,8 @@
 # Get the dependencies.
 cd $SRC
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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
 
diff --git a/kokoro/scripts/linux/build.sh b/kokoro/scripts/linux/build.sh
index 8fb7bdd..347f353 100644
--- a/kokoro/scripts/linux/build.sh
+++ b/kokoro/scripts/linux/build.sh
@@ -46,7 +46,7 @@
 ADDITIONAL_CMAKE_FLAGS=""
 if [ $CONFIG = "ASAN" ]
 then
-  ADDITIONAL_CMAKE_FLAGS="SPIRV_USE_SANITIZER=address,bounds,null"
+  ADDITIONAL_CMAKE_FLAGS="-DSPIRV_USE_SANITIZER=address,bounds,null"
   [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; }
 elif [ $CONFIG = "COVERAGE" ]
 then
@@ -68,14 +68,11 @@
 
 cd $SRC
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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 https://github.com/protocolbuffers/protobuf   external/protobuf
-pushd external/protobuf
-git fetch --all --tags --prune
-git checkout v3.7.1
-popd
+git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
 
 mkdir build && cd $SRC/build
 
diff --git a/kokoro/scripts/macos/build.sh b/kokoro/scripts/macos/build.sh
index 5a3af43..44c9a41 100644
--- a/kokoro/scripts/macos/build.sh
+++ b/kokoro/scripts/macos/build.sh
@@ -32,14 +32,11 @@
 
 cd $SRC
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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 https://github.com/protocolbuffers/protobuf   external/protobuf
-pushd external/protobuf
-git fetch --all --tags --prune
-git checkout v3.7.1
-popd
+git clone --depth=1 --branch v3.13.0 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 a4f2bf0..fa7a71a 100644
--- a/kokoro/scripts/windows/build.bat
+++ b/kokoro/scripts/windows/build.bat
@@ -26,14 +26,11 @@
 
 cd %SRC%
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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 https://github.com/protocolbuffers/protobuf   external/protobuf
-pushd external\protobuf
-git fetch --all --tags --prune
-git checkout v3.7.1
-popd
+git clone --depth=1 --branch v3.13.0 https://github.com/protocolbuffers/protobuf external/protobuf
 
 :: #########################################
 :: set up msvc build env
diff --git a/kokoro/windows-msvc-2015-release-bazel/build.bat b/kokoro/windows-msvc-2015-release-bazel/build.bat
index ddb4f54..2f721af 100644
--- a/kokoro/windows-msvc-2015-release-bazel/build.bat
+++ b/kokoro/windows-msvc-2015-release-bazel/build.bat
@@ -24,7 +24,8 @@
 :: Get dependencies
 cd %SRC%
 git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
-git clone --depth=1 https://github.com/google/googletest          external/googletest
+git clone https://github.com/google/googletest          external/googletest
+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
 
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 708ca84..65087f2 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -19,8 +19,8 @@
 
 # For now, assume the DebugInfo grammar file is in the current directory.
 # It might migrate to SPIRV-Headers.
-set(DEBUGINFO_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst.debuginfo.grammar.json")
-set(CLDEBUGINFO100_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst.opencl.debuginfo.100.grammar.json")
+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")
 
 # 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
@@ -112,7 +112,7 @@
 
 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-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json")
+  set(GRAMMAR_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.${VENDOR_TABLE}.grammar.json")
   add_custom_command(OUTPUT ${INSTS_FILE}
     COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT}
       --extinst-vendor-grammar=${GRAMMAR_FILE}
@@ -148,6 +148,7 @@
 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.clspvreflection" "clspvreflection" "")
 spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE})
 spvtools_extinst_lang_headers("OpenCLDebugInfo100" ${CLDEBUGINFO100_GRAMMAR_JSON_FILE})
 
@@ -345,47 +346,64 @@
 
 spvtools_pch(SPIRV_SOURCES pch_source)
 
-add_library(${SPIRV_TOOLS} ${SPIRV_SOURCES})
-spvtools_default_compile_options(${SPIRV_TOOLS})
-target_include_directories(${SPIRV_TOOLS}
-  PUBLIC
-    $<BUILD_INTERFACE:${spirv-tools_SOURCE_DIR}/include>
-    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
-  PRIVATE ${spirv-tools_BINARY_DIR}
-  PRIVATE ${SPIRV_HEADER_INCLUDE_DIR}
+# spirv_tools_default_target_options() sets the target options that are common
+# for all ${SPIRV_TOOLS} targets.
+function(spirv_tools_default_target_options target)
+  spvtools_default_compile_options(${target})
+  target_include_directories(${target}
+    PUBLIC
+      $<BUILD_INTERFACE:${spirv-tools_SOURCE_DIR}/include>
+      $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+    PRIVATE ${spirv-tools_BINARY_DIR}
+    PRIVATE ${SPIRV_HEADER_INCLUDE_DIR}
   )
-set_property(TARGET ${SPIRV_TOOLS} PROPERTY FOLDER "SPIRV-Tools libraries")
-spvtools_check_symbol_exports(${SPIRV_TOOLS})
-add_dependencies( ${SPIRV_TOOLS} core_tables enum_string_mapping extinst_tables )
+  set_property(TARGET ${target} PROPERTY FOLDER "SPIRV-Tools libraries")
+  spvtools_check_symbol_exports(${target})
+  add_dependencies(${target} core_tables enum_string_mapping extinst_tables)
+endfunction()
 
+# Always build ${SPIRV_TOOLS}-shared. This is expected distro packages, and
+# unlike the other SPIRV_TOOLS target, defaults to hidden symbol visibility.
 add_library(${SPIRV_TOOLS}-shared SHARED ${SPIRV_SOURCES})
-spvtools_default_compile_options(${SPIRV_TOOLS}-shared)
-target_include_directories(${SPIRV_TOOLS}-shared
-  PUBLIC
-    $<BUILD_INTERFACE:${spirv-tools_SOURCE_DIR}/include>
-    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
-  PRIVATE ${spirv-tools_BINARY_DIR}
-  PRIVATE ${SPIRV_HEADER_INCLUDE_DIR}
-  )
+spirv_tools_default_target_options(${SPIRV_TOOLS}-shared)
 set_target_properties(${SPIRV_TOOLS}-shared PROPERTIES CXX_VISIBILITY_PRESET hidden)
-set_property(TARGET ${SPIRV_TOOLS}-shared PROPERTY FOLDER "SPIRV-Tools libraries")
-spvtools_check_symbol_exports(${SPIRV_TOOLS}-shared)
 target_compile_definitions(${SPIRV_TOOLS}-shared
   PRIVATE SPIRV_TOOLS_IMPLEMENTATION
   PUBLIC SPIRV_TOOLS_SHAREDLIB
 )
-add_dependencies( ${SPIRV_TOOLS}-shared core_tables enum_string_mapping extinst_tables )
+
+if(SPIRV_TOOLS_BUILD_STATIC)
+  add_library(${SPIRV_TOOLS}-static STATIC ${SPIRV_SOURCES})
+  spirv_tools_default_target_options(${SPIRV_TOOLS}-static)
+  # The static target does not have the '-static' suffix.
+  set_target_properties(${SPIRV_TOOLS}-static PROPERTIES OUTPUT_NAME "${SPIRV_TOOLS}")
+
+  # Create the "${SPIRV_TOOLS}" target as an alias to either "${SPIRV_TOOLS}-static"
+  # or "${SPIRV_TOOLS}-shared" depending on the value of BUILD_SHARED_LIBS.
+  if(BUILD_SHARED_LIBS)
+    add_library(${SPIRV_TOOLS} ALIAS ${SPIRV_TOOLS}-shared)
+  else()
+    add_library(${SPIRV_TOOLS} ALIAS ${SPIRV_TOOLS}-static)
+  endif()
+
+  set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS}-static ${SPIRV_TOOLS}-shared)
+else()
+  add_library(${SPIRV_TOOLS} ${SPIRV_TOOLS_LIBRARY_TYPE} ${SPIRV_SOURCES})
+  spirv_tools_default_target_options(${SPIRV_TOOLS})
+  set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared)
+endif()
 
 if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
   find_library(LIBRT rt)
   if(LIBRT)
-    target_link_libraries(${SPIRV_TOOLS} ${LIBRT})
-    target_link_libraries(${SPIRV_TOOLS}-shared ${LIBRT})
+    foreach(target ${SPIRV_TOOLS_TARGETS})
+      target_link_libraries(${target} ${LIBRT})
+    endforeach()
   endif()
 endif()
 
 if(ENABLE_SPIRV_TOOLS_INSTALL)
-  install(TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared EXPORT ${SPIRV_TOOLS}Targets
+  install(TARGETS ${SPIRV_TOOLS_TARGETS} EXPORT ${SPIRV_TOOLS}Targets
     RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
     ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
@@ -402,7 +420,7 @@
   install(FILES ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake DESTINATION ${PACKAGE_DIR})
 endif(ENABLE_SPIRV_TOOLS_INSTALL)
 
-if(MSVC)
+if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
   # Enable parallel builds across four cores for this lib
   add_definitions(/MP4)
 endif()
diff --git a/source/binary.cpp b/source/binary.cpp
index f16bf52..75a997d 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -45,6 +45,14 @@
   // TODO: Validation checking?
   pHeader->magic = spvFixWord(binary->code[SPV_INDEX_MAGIC_NUMBER], endian);
   pHeader->version = spvFixWord(binary->code[SPV_INDEX_VERSION_NUMBER], endian);
+  // Per 2.3.1 version's high and low bytes are 0
+  if ((pHeader->version & 0x000000ff) || pHeader->version & 0xff000000)
+    return SPV_ERROR_INVALID_BINARY;
+  // Minimum version was 1.0 and max version is defined by SPV_VERSION.
+  if (pHeader->version < SPV_SPIRV_VERSION_WORD(1, 0) ||
+      pHeader->version > SPV_VERSION)
+    return SPV_ERROR_INVALID_BINARY;
+
   pHeader->generator =
       spvFixWord(binary->code[SPV_INDEX_GENERATOR_NUMBER], endian);
   pHeader->bound = spvFixWord(binary->code[SPV_INDEX_BOUND], endian);
diff --git a/source/cfa.h b/source/cfa.h
index 97ef398..0d09014 100644
--- a/source/cfa.h
+++ b/source/cfa.h
@@ -127,7 +127,7 @@
 template <class BB>
 bool CFA<BB>::FindInWorkList(const std::vector<block_info>& work_list,
                              uint32_t id) {
-  for (const auto b : work_list) {
+  for (const auto& b : work_list) {
     if (b.block->id() == id) return true;
   }
   return false;
diff --git a/source/disassemble.cpp b/source/disassemble.cpp
index af30ce0..e763251 100644
--- a/source/disassemble.cpp
+++ b/source/disassemble.cpp
@@ -54,6 +54,7 @@
         indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options)
                     ? kStandardIndent
                     : 0),
+        comment_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COMMENT, options)),
         text_(),
         out_(print_ ? out_stream() : out_stream(text_)),
         stream_(out_.get()),
@@ -118,6 +119,7 @@
   const bool print_;  // Should we also print to the standard output stream?
   const bool color_;  // Should we print in colour?
   const int indent_;  // How much to indent. 0 means don't indent
+  const int comment_;        // Should we comment the source
   spv_endianness_t endian_;  // The detected endianness of the binary.
   std::stringstream text_;   // Captures the text, if not printing.
   out_stream out_;  // The Output stream.  Either to text_ or standard output.
@@ -126,6 +128,9 @@
   const bool show_byte_offset_;  // Should we print byte offset, in hex?
   size_t byte_offset_;           // The number of bytes processed so far.
   spvtools::NameMapper name_mapper_;
+  bool inserted_decoration_space_ = false;
+  bool inserted_debug_space_ = false;
+  bool inserted_type_space_ = false;
 };
 
 spv_result_t Disassembler::HandleHeader(spv_endianness_t endian,
@@ -134,7 +139,6 @@
   endian_ = endian;
 
   if (header_) {
-    SetGrey();
     const char* generator_tool =
         spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator));
     stream_ << "; SPIR-V\n"
@@ -150,7 +154,6 @@
     stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n"
             << "; Bound: " << id_bound << "\n"
             << "; Schema: " << schema << "\n";
-    ResetColor();
   }
 
   byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t);
@@ -160,6 +163,32 @@
 
 spv_result_t Disassembler::HandleInstruction(
     const spv_parsed_instruction_t& inst) {
+  auto opcode = static_cast<SpvOp>(inst.opcode);
+  if (comment_ && opcode == SpvOpFunction) {
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Function " << name_mapper_(inst.result_id) << std::endl;
+  }
+  if (comment_ && !inserted_decoration_space_ &&
+      spvOpcodeIsDecoration(opcode)) {
+    inserted_decoration_space_ = true;
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Annotations" << std::endl;
+  }
+  if (comment_ && !inserted_debug_space_ && spvOpcodeIsDebug(opcode)) {
+    inserted_debug_space_ = true;
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Debug Information" << std::endl;
+  }
+  if (comment_ && !inserted_type_space_ && spvOpcodeGeneratesType(opcode)) {
+    inserted_type_space_ = true;
+    stream_ << std::endl;
+    stream_ << std::string(indent_, ' ');
+    stream_ << "; Types, variables and constants" << std::endl;
+  }
+
   if (inst.result_id) {
     SetBlue();
     const std::string id_name = name_mapper_(inst.result_id);
@@ -172,7 +201,7 @@
     stream_ << std::string(indent_, ' ');
   }
 
-  stream_ << "Op" << spvOpcodeString(static_cast<SpvOp>(inst.opcode));
+  stream_ << "Op" << spvOpcodeString(opcode);
 
   for (uint16_t i = 0; i < inst.num_operands; i++) {
     const spv_operand_type_t type = inst.operands[i].type;
@@ -182,6 +211,12 @@
     EmitOperand(inst, i);
   }
 
+  if (comment_ && opcode == SpvOpName) {
+    const spv_parsed_operand_t& operand = inst.operands[0];
+    const uint32_t word = inst.words[operand.offset];
+    stream_ << "  ; id %" << word;
+  }
+
   if (show_byte_offset_) {
     SetGrey();
     auto saved_flags = stream_.flags();
diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp
index e69c3c9..3471ebe 100644
--- a/source/ext_inst.cpp
+++ b/source/ext_inst.cpp
@@ -28,6 +28,7 @@
 
 #include "debuginfo.insts.inc"
 #include "glsl.std.450.insts.inc"
+#include "nonsemantic.clspvreflection.insts.inc"
 #include "opencl.debuginfo.100.insts.inc"
 #include "opencl.std.insts.inc"
 
@@ -54,6 +55,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_CLSPVREFLECTION,
+     ARRAY_SIZE(nonsemantic_clspvreflection_entries),
+     nonsemantic_clspvreflection_entries},
 };
 
 static const spv_ext_inst_table_t kTable_1_0 = {ARRAY_SIZE(kGroups_1_0),
@@ -123,6 +127,9 @@
   if (!strcmp("OpenCL.DebugInfo.100", name)) {
     return SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100;
   }
+  if (!strncmp("NonSemantic.ClspvReflection.", name, 28)) {
+    return SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION;
+  }
   // ensure to add any known non-semantic extended instruction sets
   // above this point, and update spvExtInstIsNonSemantic()
   if (!strncmp("NonSemantic.", name, 12)) {
@@ -132,7 +139,8 @@
 }
 
 bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) {
-  if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN) {
+  if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN ||
+      type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) {
     return true;
   }
   return false;
diff --git a/source/extinst.debuginfo.grammar.json b/source/extinst.debuginfo.grammar.json
deleted file mode 100644
index 9212f6f..0000000
--- a/source/extinst.debuginfo.grammar.json
+++ /dev/null
@@ -1,568 +0,0 @@
-{
-  "copyright" : [
-    "Copyright (c) 2017 The Khronos Group Inc.",
-    "",
-    "Permission is hereby granted, free of charge, to any person obtaining a copy",
-    "of this software and/or associated documentation files (the \"Materials\"),",
-    "to deal in the Materials without restriction, including without limitation",
-    "the rights to use, copy, modify, merge, publish, distribute, sublicense,",
-    "and/or sell copies of the Materials, and to permit persons to whom the",
-    "Materials are furnished to do so, subject to the following conditions:",
-    "",
-    "The above copyright notice and this permission notice shall be included in",
-    "all copies or substantial portions of the Materials.",
-    "",
-    "MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS",
-    "STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND",
-    "HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ ",
-    "",
-    "THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS",
-    "OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
-    "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL",
-    "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
-    "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING",
-    "FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS",
-    "IN THE MATERIALS."
-  ],
-  "version" : 100,
-  "revision" : 1,
-  "instructions" : [
-    {
-      "opname" : "DebugInfoNone",
-      "opcode" : 0
-    },
-    {
-      "opname" : "DebugCompilationUnit",
-      "opcode" : 1,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Version'" },
-        { "kind" : "LiteralInteger", "name" : "'DWARF Version'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeBasic",
-      "opcode" : 2,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugBaseTypeAttributeEncoding", "name" : "'Encoding'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypePointer",
-      "opcode" : 3,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "StorageClass", "name" : "'Storage Class'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Literal Flags'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeQualifier",
-      "opcode" : 4,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "DebugTypeQualifier", "name" : "'Type Qualifier'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeArray",
-      "opcode" : 5,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "IdRef", "name" : "'Component Counts'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeVector",
-      "opcode" : 6,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "LiteralInteger", "name" : "'Component Count'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypedef",
-      "opcode" : 7,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeFunction",
-      "opcode" : 8,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Return Type'" },
-        { "kind" : "IdRef", "name" : "'Paramter Types'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeEnum",
-      "opcode" : 9,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Underlying Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "PairIdRefIdRef", "name" : "'Value, Name, Value, Name, ...'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeComposite",
-      "opcode" : 10,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "DebugCompositeType", "name" : "'Tag'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "IdRef", "name" : "'Members'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeMember",
-      "opcode" : 11,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Offset'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeInheritance",
-      "opcode" : 12,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Child'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Offset'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypePtrToMember",
-      "opcode" : 13,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Member Type'" },
-        { "kind" : "IdRef", "name" : "'Parent'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplate",
-      "opcode" : 14,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Target'" },
-        { "kind" : "IdRef", "name" : "'Parameters'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplateParameter",
-      "opcode" : 15,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Actual Type'" },
-        { "kind" : "IdRef", "name" : "'Value'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplateTemplateParameter",
-      "opcode" : 16,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Template Name'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplateParameterPack",
-      "opcode" : 17,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Template Parameters'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugGlobalVariable",
-      "opcode" : 18,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Linkage Name'" },
-        { "kind" : "IdRef", "name" : "'Variable'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "IdRef", "name" : "'Static Member Declaration'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugFunctionDeclaration",
-      "opcode" : 19,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Linkage Name'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" }
-      ]
-    },
-    {
-      "opname" : "DebugFunction",
-      "opcode" : 20,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Linkage Name'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "LiteralInteger", "name" : "'Scope Line'" },
-        { "kind" : "IdRef", "name" : "'Function'" },
-        { "kind" : "IdRef", "name" : "'Declaration'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugLexicalBlock",
-      "opcode" : 21,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Name'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugLexicalBlockDiscriminator",
-      "opcode" : 22,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Scope'" },
-        { "kind" : "LiteralInteger", "name" : "'Discriminator'" },
-        { "kind" : "IdRef", "name" : "'Parent'" }
-      ]
-    },
-    {
-      "opname" : "DebugScope",
-      "opcode" : 23,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Scope'" },
-        { "kind" : "IdRef", "name" : "'Inlined At'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugNoScope",
-      "opcode" : 24
-    },
-    {
-      "opname" : "DebugInlinedAt",
-      "opcode" : 25,
-      "operands" : [
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "IdRef", "name" : "'Scope'" },
-        { "kind" : "IdRef", "name" : "'Inlined'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugLocalVariable",
-      "opcode" : 26,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "LiteralInteger", "name" : "'Arg Number'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugInlinedVariable",
-      "opcode" : 27,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Variable'" },
-        { "kind" : "IdRef", "name" : "'Inlined'" }
-      ]
-    },
-    {
-      "opname" : "DebugDeclare",
-      "opcode" : 28,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Local Variable'" },
-        { "kind" : "IdRef", "name" : "'Variable'" },
-        { "kind" : "IdRef", "name" : "'Expression'" }
-      ]
-    },
-    {
-      "opname" : "DebugValue",
-      "opcode" : 29,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Value'" },
-        { "kind" : "IdRef", "name" : "'Expression'" },
-        { "kind" : "IdRef", "name" : "'Indexes'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugOperation",
-      "opcode" : 30,
-      "operands" : [
-        { "kind" : "DebugOperation", "name" : "'OpCode'" },
-        { "kind" : "LiteralInteger", "name" : "'Operands ...'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugExpression",
-      "opcode" : 31,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Operands ...'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugMacroDef",
-      "opcode" : 32,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugMacroUndef",
-      "opcode" : 33,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "IdRef", "name" : "'Macro'" }
-      ]
-    }
-  ],
-  "operand_kinds" : [
-    {
-      "category" : "BitEnum",
-      "kind" : "DebugInfoFlags",
-      "enumerants" : [
-        {
-          "enumerant" : "FlagIsProtected",
-          "value" : "0x01"
-        },
-        {
-          "enumerant" : "FlagIsPrivate",
-          "value" : "0x02"
-        },
-        {
-          "enumerant" : "FlagIsPublic",
-          "value" : "0x03"
-        },
-        {
-          "enumerant" : "FlagIsLocal",
-          "value" : "0x04"
-        },
-        {
-          "enumerant" : "FlagIsDefinition",
-          "value" : "0x08"
-        },
-        {
-          "enumerant" : "FlagFwdDecl",
-          "value" : "0x10"
-        },
-        {
-          "enumerant" : "FlagArtificial",
-          "value" : "0x20"
-        },
-        {
-          "enumerant" : "FlagExplicit",
-          "value" : "0x40"
-        },
-        {
-          "enumerant" : "FlagPrototyped",
-          "value" : "0x80"
-        },
-        {
-          "enumerant" : "FlagObjectPointer",
-          "value" : "0x100"
-        },
-        {
-          "enumerant" : "FlagStaticMember",
-          "value" : "0x200"
-        },
-        {
-          "enumerant" : "FlagIndirectVariable",
-          "value" : "0x400"
-        },
-        {
-          "enumerant" : "FlagLValueReference",
-          "value" : "0x800"
-        },
-        {
-          "enumerant" : "FlagRValueReference",
-          "value" : "0x1000"
-        },
-        {
-          "enumerant" : "FlagIsOptimized",
-          "value" : "0x2000"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugBaseTypeAttributeEncoding",
-      "enumerants" : [
-        {
-          "enumerant" : "Unspecified",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "Address",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "Boolean",
-          "value" : "2"
-        },
-        {
-          "enumerant" : "Float",
-          "value" : "4"
-        },
-        {
-          "enumerant" : "Signed",
-          "value" : "5"
-        },
-        {
-          "enumerant" : "SignedChar",
-          "value" : "6"
-        },
-        {
-          "enumerant" : "Unsigned",
-          "value" : "7"
-        },
-        {
-          "enumerant" : "UnsignedChar",
-          "value" : "8"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugCompositeType",
-      "enumerants" : [
-        {
-          "enumerant" : "Class",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "Structure",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "Union",
-          "value" : "2"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugTypeQualifier",
-      "enumerants" : [
-        {
-          "enumerant" : "ConstType",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "VolatileType",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "RestrictType",
-          "value" : "2"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugOperation",
-      "enumerants" : [
-        {
-          "enumerant" : "Deref",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "Plus",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "Minus",
-          "value" : "2"
-        },
-        {
-          "enumerant" : "PlusUconst",
-          "value" : "3",
-          "parameters" : [
-             { "kind" : "LiteralInteger" }
-          ]
-        },
-        {
-          "enumerant" : "BitPiece",
-          "value" : "4",
-          "parameters" : [
-             { "kind" : "LiteralInteger" },
-             { "kind" : "LiteralInteger" }
-          ]
-        },
-        {
-          "enumerant" : "Swap",
-          "value" : "5"
-        },
-        {
-          "enumerant" : "Xderef",
-          "value" : "6"
-        },
-        {
-          "enumerant" : "StackValue",
-          "value" : "7"
-        },
-        {
-          "enumerant" : "Constu",
-          "value" : "8",
-          "parameters" : [
-             { "kind" : "LiteralInteger" }
-          ]
-        }
-      ]
-    }
-  ]
-}
diff --git a/source/extinst.opencl.debuginfo.100.grammar.json b/source/extinst.opencl.debuginfo.100.grammar.json
deleted file mode 100644
index 08062be..0000000
--- a/source/extinst.opencl.debuginfo.100.grammar.json
+++ /dev/null
@@ -1,632 +0,0 @@
-{
-  "copyright" : [
-    "Copyright (c) 2018 The Khronos Group Inc.",
-    "",
-    "Permission is hereby granted, free of charge, to any person obtaining a copy",
-    "of this software and/or associated documentation files (the \"Materials\"),",
-    "to deal in the Materials without restriction, including without limitation",
-    "the rights to use, copy, modify, merge, publish, distribute, sublicense,",
-    "and/or sell copies of the Materials, and to permit persons to whom the",
-    "Materials are furnished to do so, subject to the following conditions:",
-    "",
-    "The above copyright notice and this permission notice shall be included in",
-    "all copies or substantial portions of the Materials.",
-    "",
-    "MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS",
-    "STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND",
-    "HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ ",
-    "",
-    "THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS",
-    "OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,",
-    "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL",
-    "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER",
-    "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING",
-    "FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS",
-    "IN THE MATERIALS."
-  ],
-  "version" : 200,
-  "revision" : 2,
-  "instructions" : [
-    {
-      "opname" : "DebugInfoNone",
-      "opcode" : 0
-    },
-    {
-      "opname" : "DebugCompilationUnit",
-      "opcode" : 1,
-      "operands" : [
-        { "kind" : "LiteralInteger", "name" : "'Version'" },
-        { "kind" : "LiteralInteger", "name" : "'DWARF Version'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "SourceLanguage", "name" : "'Language'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeBasic",
-      "opcode" : 2,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugBaseTypeAttributeEncoding", "name" : "'Encoding'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypePointer",
-      "opcode" : 3,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "StorageClass", "name" : "'Storage Class'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeQualifier",
-      "opcode" : 4,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "DebugTypeQualifier", "name" : "'Type Qualifier'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeArray",
-      "opcode" : 5,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "IdRef", "name" : "'Component Counts'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeVector",
-      "opcode" : 6,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "LiteralInteger", "name" : "'Component Count'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypedef",
-      "opcode" : 7,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Base Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeFunction",
-      "opcode" : 8,
-      "operands" : [
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "IdRef", "name" : "'Return Type'" },
-        { "kind" : "IdRef", "name" : "'Parameter Types'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeEnum",
-      "opcode" : 9,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Underlying Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "PairIdRefIdRef", "name" : "'Value, Name, Value, Name, ...'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeComposite",
-      "opcode" : 10,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "DebugCompositeType", "name" : "'Tag'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Linkage Name'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "IdRef", "name" : "'Members'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeMember",
-      "opcode" : 11,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Offset'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeInheritance",
-      "opcode" : 12,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Child'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Offset'" },
-        { "kind" : "IdRef", "name" : "'Size'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypePtrToMember",
-      "opcode" : 13,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Member Type'" },
-        { "kind" : "IdRef", "name" : "'Parent'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplate",
-      "opcode" : 14,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Target'" },
-        { "kind" : "IdRef", "name" : "'Parameters'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplateParameter",
-      "opcode" : 15,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Actual Type'" },
-        { "kind" : "IdRef", "name" : "'Value'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplateTemplateParameter",
-      "opcode" : 16,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Template Name'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" }
-      ]
-    },
-    {
-      "opname" : "DebugTypeTemplateParameterPack",
-      "opcode" : 17,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Template Parameters'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugGlobalVariable",
-      "opcode" : 18,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Linkage Name'" },
-        { "kind" : "IdRef", "name" : "'Variable'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "IdRef", "name" : "'Static Member Declaration'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugFunctionDeclaration",
-      "opcode" : 19,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Linkage Name'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" }
-      ]
-    },
-    {
-      "opname" : "DebugFunction",
-      "opcode" : 20,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Linkage Name'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "LiteralInteger", "name" : "'Scope Line'" },
-        { "kind" : "IdRef", "name" : "'Function'" },
-        { "kind" : "IdRef", "name" : "'Declaration'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugLexicalBlock",
-      "opcode" : 21,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "IdRef", "name" : "'Name'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugLexicalBlockDiscriminator",
-      "opcode" : 22,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Discriminator'" },
-        { "kind" : "IdRef", "name" : "'Parent'" }
-      ]
-    },
-    {
-      "opname" : "DebugScope",
-      "opcode" : 23,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Scope'" },
-        { "kind" : "IdRef", "name" : "'Inlined At'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugNoScope",
-      "opcode" : 24
-    },
-    {
-      "opname" : "DebugInlinedAt",
-      "opcode" : 25,
-      "operands" : [
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "IdRef", "name" : "'Scope'" },
-        { "kind" : "IdRef", "name" : "'Inlined'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugLocalVariable",
-      "opcode" : 26,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Type'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" },
-        { "kind" : "DebugInfoFlags", "name" : "'Flags'" },
-        { "kind" : "LiteralInteger", "name" : "'Arg Number'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugInlinedVariable",
-      "opcode" : 27,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Variable'" },
-        { "kind" : "IdRef", "name" : "'Inlined'" }
-      ]
-    },
-    {
-      "opname" : "DebugDeclare",
-      "opcode" : 28,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Local Variable'" },
-        { "kind" : "IdRef", "name" : "'Variable'" },
-        { "kind" : "IdRef", "name" : "'Expression'" }
-      ]
-    },
-    {
-      "opname" : "DebugValue",
-      "opcode" : 29,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Local Variable'" },
-        { "kind" : "IdRef", "name" : "'Value'" },
-        { "kind" : "IdRef", "name" : "'Expression'" },
-        { "kind" : "IdRef", "name" : "'Indexes'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugOperation",
-      "opcode" : 30,
-      "operands" : [
-        { "kind" : "DebugOperation", "name" : "'OpCode'" },
-        { "kind" : "LiteralInteger", "name" : "'Operands ...'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugExpression",
-      "opcode" : 31,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Operands ...'", "quantifier" : "*" }
-      ]
-    },
-    {
-      "opname" : "DebugMacroDef",
-      "opcode" : 32,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" }
-      ]
-    },
-    {
-      "opname" : "DebugMacroUndef",
-      "opcode" : 33,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "IdRef", "name" : "'Macro'" }
-      ]
-    },
-    {
-      "opname" : "DebugImportedEntity",
-      "opcode" : 34,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'Name'" },
-        { "kind" : "DebugImportedEntity", "name" : "'Tag'" },
-        { "kind" : "IdRef", "name" : "'Source'" },
-        { "kind" : "IdRef", "name" : "'Entity'" },
-        { "kind" : "LiteralInteger", "name" : "'Line'" },
-        { "kind" : "LiteralInteger", "name" : "'Column'" },
-        { "kind" : "IdRef", "name" : "'Parent'" }
-      ]
-    },
-    {
-      "opname" : "DebugSource",
-      "opcode" : 35,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'File'" },
-        { "kind" : "IdRef", "name" : "'Text'", "quantifier" : "?" }
-      ]
-    }
-  ],
-  "operand_kinds" : [
-    {
-      "category" : "BitEnum",
-      "kind" : "DebugInfoFlags",
-      "enumerants" : [
-        {
-          "enumerant" : "FlagIsProtected",
-          "value" : "0x01"
-        },
-        {
-          "enumerant" : "FlagIsPrivate",
-          "value" : "0x02"
-        },
-        {
-          "enumerant" : "FlagIsPublic",
-          "value" : "0x03"
-        },
-        {
-          "enumerant" : "FlagIsLocal",
-          "value" : "0x04"
-        },
-        {
-          "enumerant" : "FlagIsDefinition",
-          "value" : "0x08"
-        },
-        {
-          "enumerant" : "FlagFwdDecl",
-          "value" : "0x10"
-        },
-        {
-          "enumerant" : "FlagArtificial",
-          "value" : "0x20"
-        },
-        {
-          "enumerant" : "FlagExplicit",
-          "value" : "0x40"
-        },
-        {
-          "enumerant" : "FlagPrototyped",
-          "value" : "0x80"
-        },
-        {
-          "enumerant" : "FlagObjectPointer",
-          "value" : "0x100"
-        },
-        {
-          "enumerant" : "FlagStaticMember",
-          "value" : "0x200"
-        },
-        {
-          "enumerant" : "FlagIndirectVariable",
-          "value" : "0x400"
-        },
-        {
-          "enumerant" : "FlagLValueReference",
-          "value" : "0x800"
-        },
-        {
-          "enumerant" : "FlagRValueReference",
-          "value" : "0x1000"
-        },
-        {
-          "enumerant" : "FlagIsOptimized",
-          "value" : "0x2000"
-        },
-        {
-          "enumerant" : "FlagIsEnumClass",
-          "value" : "0x4000"
-        },
-        {
-          "enumerant" : "FlagTypePassByValue",
-          "value" : "0x8000"
-        },
-        {
-          "enumerant" : "FlagTypePassByReference",
-          "value" : "0x10000"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugBaseTypeAttributeEncoding",
-      "enumerants" : [
-        {
-          "enumerant" : "Unspecified",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "Address",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "Boolean",
-          "value" : "2"
-        },
-        {
-          "enumerant" : "Float",
-          "value" : "3"
-        },
-        {
-          "enumerant" : "Signed",
-          "value" : "4"
-        },
-        {
-          "enumerant" : "SignedChar",
-          "value" : "5"
-        },
-        {
-          "enumerant" : "Unsigned",
-          "value" : "6"
-        },
-        {
-          "enumerant" : "UnsignedChar",
-          "value" : "7"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugCompositeType",
-      "enumerants" : [
-        {
-          "enumerant" : "Class",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "Structure",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "Union",
-          "value" : "2"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugTypeQualifier",
-      "enumerants" : [
-        {
-          "enumerant" : "ConstType",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "VolatileType",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "RestrictType",
-          "value" : "2"
-        },
-        {
-          "enumerant" : "AtomicType",
-          "value" : "3"
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugOperation",
-      "enumerants" : [
-        {
-          "enumerant" : "Deref",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "Plus",
-          "value" : "1"
-        },
-        {
-          "enumerant" : "Minus",
-          "value" : "2"
-        },
-        {
-          "enumerant" : "PlusUconst",
-          "value" : "3",
-          "parameters" : [
-             { "kind" : "LiteralInteger" }
-          ]
-        },
-        {
-          "enumerant" : "BitPiece",
-          "value" : "4",
-          "parameters" : [
-             { "kind" : "LiteralInteger" },
-             { "kind" : "LiteralInteger" }
-          ]
-        },
-        {
-          "enumerant" : "Swap",
-          "value" : "5"
-        },
-        {
-          "enumerant" : "Xderef",
-          "value" : "6"
-        },
-        {
-          "enumerant" : "StackValue",
-          "value" : "7"
-        },
-        {
-          "enumerant" : "Constu",
-          "value" : "8",
-          "parameters" : [
-             { "kind" : "LiteralInteger" }
-          ]
-        },
-        {
-          "enumerant" : "Fragment",
-          "value" : "9",
-          "parameters" : [
-             { "kind" : "LiteralInteger" },
-             { "kind" : "LiteralInteger" }
-          ]
-        }
-      ]
-    },
-    {
-      "category" : "ValueEnum",
-      "kind" : "DebugImportedEntity",
-      "enumerants" : [
-        {
-          "enumerant" : "ImportedModule",
-          "value" : "0"
-        },
-        {
-          "enumerant" : "ImportedDeclaration",
-          "value" : "1"
-	}
-      ]
-    }
-  ]
-}
diff --git a/source/extinst.spv-amd-gcn-shader.grammar.json b/source/extinst.spv-amd-gcn-shader.grammar.json
deleted file mode 100644
index e18251b..0000000
--- a/source/extinst.spv-amd-gcn-shader.grammar.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-  "revision" : 2,
-  "instructions" : [
-    {
-      "opname" : "CubeFaceIndexAMD",
-      "opcode" : 1,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'P'" }
-      ],
-      "extensions" : [ "SPV_AMD_gcn_shader" ]
-    },
-    {
-      "opname" : "CubeFaceCoordAMD",
-      "opcode" : 2,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'P'" }
-      ],
-      "extensions" : [ "SPV_AMD_gcn_shader" ]
-    },
-    {
-      "opname" : "TimeAMD",
-      "opcode" : 3,
-      "extensions" : [ "SPV_AMD_gcn_shader" ]
-    }
-  ]
-}
diff --git a/source/extinst.spv-amd-shader-ballot.grammar.json b/source/extinst.spv-amd-shader-ballot.grammar.json
deleted file mode 100644
index 62a470e..0000000
--- a/source/extinst.spv-amd-shader-ballot.grammar.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
-  "revision" : 5,
-  "instructions" : [
-    {
-      "opname" : "SwizzleInvocationsAMD",
-      "opcode" : 1,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'data'" },
-        { "kind" : "IdRef", "name" : "'offset'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_ballot" ]
-    },
-    {
-      "opname" : "SwizzleInvocationsMaskedAMD",
-      "opcode" : 2,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'data'" },
-        { "kind" : "IdRef", "name" : "'mask'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_ballot" ]
-    },
-    {
-      "opname" : "WriteInvocationAMD",
-      "opcode" : 3,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'inputValue'" },
-        { "kind" : "IdRef", "name" : "'writeValue'" },
-        { "kind" : "IdRef", "name" : "'invocationIndex'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_ballot" ]
-    },
-    {
-      "opname" : "MbcntAMD",
-      "opcode" : 4,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'mask'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_ballot" ]
-    }
-  ]
-}
diff --git a/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json b/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json
deleted file mode 100644
index e156b1b..0000000
--- a/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "revision" : 4,
-  "instructions" : [
-    {
-      "opname" : "InterpolateAtVertexAMD",
-      "opcode" : 1,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'interpolant'" },
-        { "kind" : "IdRef", "name" : "'vertexIdx'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_explicit_vertex_parameter" ]
-    }
-  ]
-}
diff --git a/source/extinst.spv-amd-shader-trinary-minmax.grammar.json b/source/extinst.spv-amd-shader-trinary-minmax.grammar.json
deleted file mode 100644
index c681976..0000000
--- a/source/extinst.spv-amd-shader-trinary-minmax.grammar.json
+++ /dev/null
@@ -1,95 +0,0 @@
-{
-  "revision" : 4,
-  "instructions" : [
-    {
-      "opname" : "FMin3AMD",
-      "opcode" : 1,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "UMin3AMD",
-      "opcode" : 2,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "SMin3AMD",
-      "opcode" : 3,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "FMax3AMD",
-      "opcode" : 4,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "UMax3AMD",
-      "opcode" : 5,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "SMax3AMD",
-      "opcode" : 6,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "FMid3AMD",
-      "opcode" : 7,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "UMid3AMD",
-      "opcode" : 8,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    },
-    {
-      "opname" : "SMid3AMD",
-      "opcode" : 9,
-      "operands" : [
-        { "kind" : "IdRef", "name" : "'x'" },
-        { "kind" : "IdRef", "name" : "'y'" },
-        { "kind" : "IdRef", "name" : "'z'" }
-      ],
-      "extensions" : [ "SPV_AMD_shader_trinary_minmax" ]
-    }
-  ]
-}
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index c413614..d3aa9f1 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -18,9 +18,16 @@
 
   set(PROTOBUF_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/protobufs/spvtoolsfuzz.proto)
 
+  set(
+        SPIRV_FUZZ_PROTOC_COMMAND
+        "protobuf::protoc"
+        CACHE
+        STRING
+        "The command to invoke the protobuf compiler (protoc). By default it is the protobufs::protoc CMake target. It should be overridden when cross-compiling, such as for Android.")
+
   add_custom_command(
         OUTPUT protobufs/spvtoolsfuzz.pb.cc protobufs/spvtoolsfuzz.pb.h
-        COMMAND protobuf::protoc
+        COMMAND "${SPIRV_FUZZ_PROTOC_COMMAND}"
         -I=${CMAKE_CURRENT_SOURCE_DIR}/protobufs
         --cpp_out=protobufs
         ${PROTOBUF_SOURCE}
@@ -29,15 +36,26 @@
   )
 
   set(SPIRV_TOOLS_FUZZ_SOURCES
+        added_function_reducer.h
         call_graph.h
+        comparator_deep_blocks_first.h
+        counter_overflow_id_source.h
         data_descriptor.h
         equivalence_relation.h
-        fact_manager.h
+        fact_manager/constant_uniform_facts.h
+        fact_manager/data_synonym_and_id_equation_facts.h
+        fact_manager/dead_block_facts.h
+        fact_manager/fact_manager.h
+        fact_manager/irrelevant_value_facts.h
+        fact_manager/livesafe_function_facts.h
         force_render_red.h
         fuzzer.h
         fuzzer_context.h
         fuzzer_pass.h
         fuzzer_pass_add_access_chains.h
+        fuzzer_pass_add_bit_instruction_synonyms.h
+        fuzzer_pass_add_composite_extract.h
+        fuzzer_pass_add_composite_inserts.h
         fuzzer_pass_add_composite_types.h
         fuzzer_pass_add_copy_memory.h
         fuzzer_pass_add_dead_blocks.h
@@ -47,13 +65,16 @@
         fuzzer_pass_add_function_calls.h
         fuzzer_pass_add_global_variables.h
         fuzzer_pass_add_image_sample_unused_components.h
-        fuzzer_pass_add_synonyms.h
         fuzzer_pass_add_loads.h
         fuzzer_pass_add_local_variables.h
+        fuzzer_pass_add_loop_preheaders.h
+        fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
         fuzzer_pass_add_no_contraction_decorations.h
+        fuzzer_pass_add_opphi_synonyms.h
         fuzzer_pass_add_parameters.h
         fuzzer_pass_add_relaxed_decorations.h
         fuzzer_pass_add_stores.h
+        fuzzer_pass_add_synonyms.h
         fuzzer_pass_add_vector_shuffle_instructions.h
         fuzzer_pass_adjust_branch_weights.h
         fuzzer_pass_adjust_function_controls.h
@@ -64,29 +85,54 @@
         fuzzer_pass_construct_composites.h
         fuzzer_pass_copy_objects.h
         fuzzer_pass_donate_modules.h
+        fuzzer_pass_duplicate_regions_with_selections.h
+        fuzzer_pass_expand_vector_reductions.h
+        fuzzer_pass_flatten_conditional_branches.h
+        fuzzer_pass_inline_functions.h
         fuzzer_pass_invert_comparison_operators.h
+        fuzzer_pass_interchange_signedness_of_integer_operands.h
         fuzzer_pass_interchange_zero_like_constants.h
+        fuzzer_pass_make_vector_operations_dynamic.h
         fuzzer_pass_merge_blocks.h
+        fuzzer_pass_merge_function_returns.h
+        fuzzer_pass_mutate_pointers.h
         fuzzer_pass_obfuscate_constants.h
         fuzzer_pass_outline_functions.h
         fuzzer_pass_permute_blocks.h
         fuzzer_pass_permute_function_parameters.h
+        fuzzer_pass_permute_instructions.h
         fuzzer_pass_permute_phi_operands.h
+        fuzzer_pass_propagate_instructions_down.h
+        fuzzer_pass_propagate_instructions_up.h
         fuzzer_pass_push_ids_through_variables.h
+        fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
+        fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h
         fuzzer_pass_replace_copy_memories_with_loads_stores.h
         fuzzer_pass_replace_copy_objects_with_stores_loads.h
+        fuzzer_pass_replace_irrelevant_ids.h
         fuzzer_pass_replace_linear_algebra_instructions.h
         fuzzer_pass_replace_loads_stores_with_copy_memories.h
+        fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
+        fuzzer_pass_replace_opselects_with_conditional_branches.h
         fuzzer_pass_replace_parameter_with_global.h
         fuzzer_pass_replace_params_with_struct.h
         fuzzer_pass_split_blocks.h
         fuzzer_pass_swap_commutable_operands.h
         fuzzer_pass_swap_conditional_branch_operands.h
         fuzzer_pass_toggle_access_chain_instruction.h
+        fuzzer_pass_wrap_regions_in_selections.h
         fuzzer_util.h
         id_use_descriptor.h
         instruction_descriptor.h
         instruction_message.h
+        overflow_id_source.h
+        pass_management/repeated_pass_instances.h
+        pass_management/repeated_pass_manager.h
+        pass_management/repeated_pass_manager_looped_with_recommendations.h
+        pass_management/repeated_pass_manager_random_with_recommendations.h
+        pass_management/repeated_pass_manager_simple.h
+        pass_management/repeated_pass_recommender.h
+        pass_management/repeated_pass_recommender_standard.h
         protobufs/spirvfuzz_protobufs.h
         pseudo_random_generator.h
         random_generator.h
@@ -94,6 +140,7 @@
         shrinker.h
         transformation.h
         transformation_access_chain.h
+        transformation_add_bit_instruction_synonym.h
         transformation_add_constant_boolean.h
         transformation_add_constant_composite.h
         transformation_add_constant_null.h
@@ -102,12 +149,16 @@
         transformation_add_dead_block.h
         transformation_add_dead_break.h
         transformation_add_dead_continue.h
+        transformation_add_early_terminator_wrapper.h
         transformation_add_function.h
         transformation_add_global_undef.h
         transformation_add_global_variable.h
         transformation_add_image_sample_unused_components.h
         transformation_add_local_variable.h
+        transformation_add_loop_preheader.h
+        transformation_add_loop_to_create_int_constant_synonym.h
         transformation_add_no_contraction_decoration.h
+        transformation_add_opphi_synonym.h
         transformation_add_parameter.h
         transformation_add_relaxed_decoration.h
         transformation_add_spec_constant_op.h
@@ -124,26 +175,42 @@
         transformation_adjust_branch_weights.h
         transformation_composite_construct.h
         transformation_composite_extract.h
+        transformation_composite_insert.h
         transformation_compute_data_synonym_fact_closure.h
         transformation_context.h
+        transformation_duplicate_region_with_selection.h
         transformation_equation_instruction.h
+        transformation_expand_vector_reduction.h
+        transformation_flatten_conditional_branch.h
         transformation_function_call.h
+        transformation_inline_function.h
         transformation_invert_comparison_operator.h
         transformation_load.h
+        transformation_make_vector_operation_dynamic.h
         transformation_merge_blocks.h
+        transformation_merge_function_returns.h
         transformation_move_block_down.h
+        transformation_move_instruction_down.h
+        transformation_mutate_pointer.h
         transformation_outline_function.h
         transformation_permute_function_parameters.h
         transformation_permute_phi_operands.h
+        transformation_propagate_instruction_down.h
+        transformation_propagate_instruction_up.h
         transformation_push_id_through_variable.h
         transformation_record_synonymous_constants.h
+        transformation_replace_add_sub_mul_with_carrying_extended.h
         transformation_replace_boolean_constant_with_constant_binary.h
+        transformation_replace_branch_from_dead_block_with_exit.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_copy_memory_with_load_store.h
         transformation_replace_copy_object_with_store_load.h
         transformation_replace_id_with_synonym.h
+        transformation_replace_irrelevant_id.h
         transformation_replace_linear_algebra_instruction.h
         transformation_replace_load_store_with_copy_memory.h
+        transformation_replace_opphi_id_from_dead_predecessor.h
+        transformation_replace_opselect_with_conditional_branch.h
         transformation_replace_parameter_with_global.h
         transformation_replace_params_with_struct.h
         transformation_set_function_control.h
@@ -156,17 +223,29 @@
         transformation_swap_conditional_branch_operands.h
         transformation_toggle_access_chain_instruction.h
         transformation_vector_shuffle.h
+        transformation_wrap_early_terminator_in_function.h
+        transformation_wrap_region_in_selection.h
         uniform_buffer_element_descriptor.h
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
+        added_function_reducer.cpp
         call_graph.cpp
+        counter_overflow_id_source.cpp
         data_descriptor.cpp
-        fact_manager.cpp
+        fact_manager/constant_uniform_facts.cpp
+        fact_manager/data_synonym_and_id_equation_facts.cpp
+        fact_manager/dead_block_facts.cpp
+        fact_manager/fact_manager.cpp
+        fact_manager/irrelevant_value_facts.cpp
+        fact_manager/livesafe_function_facts.cpp
         force_render_red.cpp
         fuzzer.cpp
         fuzzer_context.cpp
         fuzzer_pass.cpp
         fuzzer_pass_add_access_chains.cpp
+        fuzzer_pass_add_bit_instruction_synonyms.cpp
+        fuzzer_pass_add_composite_extract.cpp
+        fuzzer_pass_add_composite_inserts.cpp
         fuzzer_pass_add_composite_types.cpp
         fuzzer_pass_add_copy_memory.cpp
         fuzzer_pass_add_dead_blocks.cpp
@@ -176,13 +255,16 @@
         fuzzer_pass_add_function_calls.cpp
         fuzzer_pass_add_global_variables.cpp
         fuzzer_pass_add_image_sample_unused_components.cpp
-        fuzzer_pass_add_synonyms.cpp
         fuzzer_pass_add_loads.cpp
         fuzzer_pass_add_local_variables.cpp
+        fuzzer_pass_add_loop_preheaders.cpp
+        fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
         fuzzer_pass_add_no_contraction_decorations.cpp
+        fuzzer_pass_add_opphi_synonyms.cpp
         fuzzer_pass_add_parameters.cpp
         fuzzer_pass_add_relaxed_decorations.cpp
         fuzzer_pass_add_stores.cpp
+        fuzzer_pass_add_synonyms.cpp
         fuzzer_pass_add_vector_shuffle_instructions.cpp
         fuzzer_pass_adjust_branch_weights.cpp
         fuzzer_pass_adjust_function_controls.cpp
@@ -193,35 +275,60 @@
         fuzzer_pass_construct_composites.cpp
         fuzzer_pass_copy_objects.cpp
         fuzzer_pass_donate_modules.cpp
+        fuzzer_pass_duplicate_regions_with_selections.cpp
+        fuzzer_pass_expand_vector_reductions.cpp
+        fuzzer_pass_flatten_conditional_branches.cpp
+        fuzzer_pass_inline_functions.cpp
         fuzzer_pass_invert_comparison_operators.cpp
+        fuzzer_pass_interchange_signedness_of_integer_operands.cpp
         fuzzer_pass_interchange_zero_like_constants.cpp
+        fuzzer_pass_make_vector_operations_dynamic.cpp
         fuzzer_pass_merge_blocks.cpp
+        fuzzer_pass_merge_function_returns.cpp
+        fuzzer_pass_mutate_pointers.cpp
         fuzzer_pass_obfuscate_constants.cpp
         fuzzer_pass_outline_functions.cpp
         fuzzer_pass_permute_blocks.cpp
         fuzzer_pass_permute_function_parameters.cpp
+        fuzzer_pass_permute_instructions.cpp
         fuzzer_pass_permute_phi_operands.cpp
+        fuzzer_pass_propagate_instructions_down.cpp
+        fuzzer_pass_propagate_instructions_up.cpp
         fuzzer_pass_push_ids_through_variables.cpp
+        fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
+        fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp
         fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
         fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
+        fuzzer_pass_replace_irrelevant_ids.cpp
         fuzzer_pass_replace_linear_algebra_instructions.cpp
         fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
+        fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
+        fuzzer_pass_replace_opselects_with_conditional_branches.cpp
         fuzzer_pass_replace_parameter_with_global.cpp
         fuzzer_pass_replace_params_with_struct.cpp
         fuzzer_pass_split_blocks.cpp
         fuzzer_pass_swap_commutable_operands.cpp
         fuzzer_pass_swap_conditional_branch_operands.cpp
         fuzzer_pass_toggle_access_chain_instruction.cpp
+        fuzzer_pass_wrap_regions_in_selections.cpp
         fuzzer_util.cpp
         id_use_descriptor.cpp
         instruction_descriptor.cpp
         instruction_message.cpp
+        overflow_id_source.cpp
+        pass_management/repeated_pass_manager.cpp
+        pass_management/repeated_pass_manager_looped_with_recommendations.cpp
+        pass_management/repeated_pass_manager_random_with_recommendations.cpp
+        pass_management/repeated_pass_manager_simple.cpp
+        pass_management/repeated_pass_recommender.cpp
+        pass_management/repeated_pass_recommender_standard.cpp
         pseudo_random_generator.cpp
         random_generator.cpp
         replayer.cpp
         shrinker.cpp
         transformation.cpp
         transformation_access_chain.cpp
+        transformation_add_bit_instruction_synonym.cpp
         transformation_add_constant_boolean.cpp
         transformation_add_constant_composite.cpp
         transformation_add_constant_null.cpp
@@ -230,12 +337,16 @@
         transformation_add_dead_block.cpp
         transformation_add_dead_break.cpp
         transformation_add_dead_continue.cpp
+        transformation_add_early_terminator_wrapper.cpp
         transformation_add_function.cpp
         transformation_add_global_undef.cpp
         transformation_add_global_variable.cpp
         transformation_add_image_sample_unused_components.cpp
         transformation_add_local_variable.cpp
+        transformation_add_loop_preheader.cpp
+        transformation_add_loop_to_create_int_constant_synonym.cpp
         transformation_add_no_contraction_decoration.cpp
+        transformation_add_opphi_synonym.cpp
         transformation_add_parameter.cpp
         transformation_add_relaxed_decoration.cpp
         transformation_add_spec_constant_op.cpp
@@ -252,26 +363,42 @@
         transformation_adjust_branch_weights.cpp
         transformation_composite_construct.cpp
         transformation_composite_extract.cpp
+        transformation_composite_insert.cpp
         transformation_compute_data_synonym_fact_closure.cpp
         transformation_context.cpp
+        transformation_duplicate_region_with_selection.cpp
         transformation_equation_instruction.cpp
+        transformation_expand_vector_reduction.cpp
+        transformation_flatten_conditional_branch.cpp
         transformation_function_call.cpp
+        transformation_inline_function.cpp
         transformation_invert_comparison_operator.cpp
         transformation_load.cpp
+        transformation_make_vector_operation_dynamic.cpp
         transformation_merge_blocks.cpp
+        transformation_merge_function_returns.cpp
         transformation_move_block_down.cpp
+        transformation_move_instruction_down.cpp
+        transformation_mutate_pointer.cpp
         transformation_outline_function.cpp
         transformation_permute_function_parameters.cpp
         transformation_permute_phi_operands.cpp
+        transformation_propagate_instruction_down.cpp
+        transformation_propagate_instruction_up.cpp
         transformation_push_id_through_variable.cpp
         transformation_record_synonymous_constants.cpp
+        transformation_replace_add_sub_mul_with_carrying_extended.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
+        transformation_replace_branch_from_dead_block_with_exit.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_copy_memory_with_load_store.cpp
         transformation_replace_copy_object_with_store_load.cpp
         transformation_replace_id_with_synonym.cpp
+        transformation_replace_irrelevant_id.cpp
         transformation_replace_linear_algebra_instruction.cpp
         transformation_replace_load_store_with_copy_memory.cpp
+        transformation_replace_opphi_id_from_dead_predecessor.cpp
+        transformation_replace_opselect_with_conditional_branch.cpp
         transformation_replace_parameter_with_global.cpp
         transformation_replace_params_with_struct.cpp
         transformation_set_function_control.cpp
@@ -284,11 +411,13 @@
         transformation_swap_conditional_branch_operands.cpp
         transformation_toggle_access_chain_instruction.cpp
         transformation_vector_shuffle.cpp
+        transformation_wrap_early_terminator_in_function.cpp
+        transformation_wrap_region_in_selection.cpp
         uniform_buffer_element_descriptor.cpp
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.cc
         )
 
-  if(MSVC)
+  if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
     # Enable parallel builds across four cores for this lib
     add_definitions(/MP4)
   endif()
@@ -298,7 +427,6 @@
   add_library(SPIRV-Tools-fuzz ${SPIRV_TOOLS_FUZZ_SOURCES})
 
   spvtools_default_compile_options(SPIRV-Tools-fuzz)
-  target_compile_definitions(SPIRV-Tools-fuzz PUBLIC -DGOOGLE_PROTOBUF_NO_RTTI -DGOOGLE_PROTOBUF_USE_UNALIGNED=0)
 
   # Compilation of the auto-generated protobuf source file will yield warnings,
   # which we have no control over and thus wish to ignore.
@@ -319,8 +447,9 @@
 
   # The fuzzer reuses a lot of functionality from the SPIRV-Tools library.
   target_link_libraries(SPIRV-Tools-fuzz
-        PUBLIC ${SPIRV_TOOLS}
+        PUBLIC ${SPIRV_TOOLS_FULL_VISIBILITY}
         PUBLIC SPIRV-Tools-opt
+        PUBLIC SPIRV-Tools-reduce
         PUBLIC protobuf::libprotobuf)
 
   set_property(TARGET SPIRV-Tools-fuzz PROPERTY FOLDER "SPIRV-Tools libraries")
diff --git a/source/fuzz/added_function_reducer.cpp b/source/fuzz/added_function_reducer.cpp
new file mode 100644
index 0000000..e7cb027
--- /dev/null
+++ b/source/fuzz/added_function_reducer.cpp
@@ -0,0 +1,302 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/added_function_reducer.h"
+
+#include "source/fuzz/instruction_message.h"
+#include "source/fuzz/replayer.h"
+#include "source/fuzz/transformation_add_function.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "source/reduce/reducer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+AddedFunctionReducer::AddedFunctionReducer(
+    spv_target_env target_env, MessageConsumer consumer,
+    const std::vector<uint32_t>& binary_in,
+    const protobufs::FactSequence& initial_facts,
+    const protobufs::TransformationSequence& transformation_sequence_in,
+    uint32_t index_of_add_function_transformation,
+    const Shrinker::InterestingnessFunction& shrinker_interestingness_function,
+    bool validate_during_replay, spv_validator_options validator_options,
+    uint32_t shrinker_step_limit, uint32_t num_existing_shrink_attempts)
+    : target_env_(target_env),
+      consumer_(std::move(consumer)),
+      binary_in_(binary_in),
+      initial_facts_(initial_facts),
+      transformation_sequence_in_(transformation_sequence_in),
+      index_of_add_function_transformation_(
+          index_of_add_function_transformation),
+      shrinker_interestingness_function_(shrinker_interestingness_function),
+      validate_during_replay_(validate_during_replay),
+      validator_options_(validator_options),
+      shrinker_step_limit_(shrinker_step_limit),
+      num_existing_shrink_attempts_(num_existing_shrink_attempts),
+      num_reducer_interestingness_function_invocations_(0) {}
+
+AddedFunctionReducer::~AddedFunctionReducer() = default;
+
+AddedFunctionReducer::AddedFunctionReducerResult AddedFunctionReducer::Run() {
+  // Replay all transformations before the AddFunction transformation, then
+  // add the raw function associated with the AddFunction transformation.
+  std::vector<uint32_t> binary_to_reduce;
+  std::unordered_set<uint32_t> irrelevant_pointee_global_variables;
+  ReplayPrefixAndAddFunction(&binary_to_reduce,
+                             &irrelevant_pointee_global_variables);
+
+  // Set up spirv-reduce to use our very specific interestingness function.
+  reduce::Reducer reducer(target_env_);
+  reducer.SetMessageConsumer(consumer_);
+  reducer.AddDefaultReductionPasses();
+  reducer.SetInterestingnessFunction(
+      [this, &irrelevant_pointee_global_variables](
+          const std::vector<uint32_t>& binary_under_reduction,
+          uint32_t /*unused*/) {
+        return InterestingnessFunctionForReducingAddedFunction(
+            binary_under_reduction, irrelevant_pointee_global_variables);
+      });
+
+  // Instruct spirv-reduce to only target the function with the id associated
+  // with the AddFunction transformation that we care about.
+  spvtools::ReducerOptions reducer_options;
+  reducer_options.set_target_function(GetAddedFunctionId());
+  // Bound the number of reduction steps that spirv-reduce can make according
+  // to the overall shrinker step limit and the number of shrink attempts that
+  // have already been tried.
+  assert(shrinker_step_limit_ > num_existing_shrink_attempts_ &&
+         "The added function reducer should not have been invoked.");
+  reducer_options.set_step_limit(shrinker_step_limit_ -
+                                 num_existing_shrink_attempts_);
+
+  // Run spirv-reduce.
+  std::vector<uint32_t> reduced_binary;
+  auto reducer_result =
+      reducer.Run(std::move(binary_to_reduce), &reduced_binary, reducer_options,
+                  validator_options_);
+  if (reducer_result != reduce::Reducer::kComplete &&
+      reducer_result != reduce::Reducer::kReachedStepLimit) {
+    return {AddedFunctionReducerResultStatus::kReductionFailed,
+            std::vector<uint32_t>(), protobufs::TransformationSequence(), 0};
+  }
+
+  // Provide the outer shrinker with an adapted sequence of transformations in
+  // which the AddFunction transformation of interest has been simplified to use
+  // the version of the added function that appears in |reduced_binary|.
+  std::vector<uint32_t> binary_out;
+  protobufs::TransformationSequence transformation_sequence_out;
+  ReplayAdaptedTransformations(reduced_binary, &binary_out,
+                               &transformation_sequence_out);
+  // We subtract 1 from |num_reducer_interestingness_function_invocations_| to
+  // account for the fact that spirv-reduce invokes its interestingness test
+  // once before reduction commences in order to check that the initial module
+  // is interesting.
+  assert(num_reducer_interestingness_function_invocations_ > 0 &&
+         "At a minimum spirv-reduce should have invoked its interestingness "
+         "test once.");
+  return {AddedFunctionReducerResultStatus::kComplete, std::move(binary_out),
+          std::move(transformation_sequence_out),
+          num_reducer_interestingness_function_invocations_ - 1};
+}
+
+bool AddedFunctionReducer::InterestingnessFunctionForReducingAddedFunction(
+    const std::vector<uint32_t>& binary_under_reduction,
+    const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables) {
+  uint32_t counter_for_shrinker_interestingness_function =
+      num_existing_shrink_attempts_ +
+      num_reducer_interestingness_function_invocations_;
+  num_reducer_interestingness_function_invocations_++;
+
+  // The reduced version of the added function must be limited to accessing
+  // global variables appearing in |irrelevant_pointee_global_variables|.  This
+  // is to guard against the possibility of spirv-reduce changing a reference
+  // to an irrelevant global to a reference to a regular global variable, which
+  // could cause the added function to change the semantics of the original
+  // module.
+  auto ir_context =
+      BuildModule(target_env_, consumer_, binary_under_reduction.data(),
+                  binary_under_reduction.size());
+  assert(ir_context != nullptr && "The binary should be parsable.");
+  for (auto& type_or_value : ir_context->module()->types_values()) {
+    if (type_or_value.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (irrelevant_pointee_global_variables.count(type_or_value.result_id())) {
+      continue;
+    }
+    if (!ir_context->get_def_use_mgr()->WhileEachUse(
+            &type_or_value,
+            [this, &ir_context](opt::Instruction* user,
+                                uint32_t /*unused*/) -> bool {
+              auto block = ir_context->get_instr_block(user);
+              if (block != nullptr &&
+                  block->GetParent()->result_id() == GetAddedFunctionId()) {
+                return false;
+              }
+              return true;
+            })) {
+      return false;
+    }
+  }
+
+  // For the binary to be deemed interesting, it must be possible to
+  // successfully apply all the transformations, with the transformation at
+  // index |index_of_add_function_transformation_| simplified to use the version
+  // of the added function from |binary_under_reduction|.
+  //
+  // This might not be the case: spirv-reduce might have removed a chunk of the
+  // added function on which future transformations depend.
+  //
+  // This is an optimization: the assumption is that having already shrunk the
+  // transformation sequence down to minimal form, all transformations have a
+  // role to play, and it's almost certainly a waste of time to invoke the
+  // shrinker's interestingness function if we have eliminated transformations
+  // that the shrinker previously tried to -- but could not -- eliminate.
+  std::vector<uint32_t> binary_out;
+  protobufs::TransformationSequence modified_transformations;
+  ReplayAdaptedTransformations(binary_under_reduction, &binary_out,
+                               &modified_transformations);
+  if (transformation_sequence_in_.transformation_size() !=
+      modified_transformations.transformation_size()) {
+    return false;
+  }
+
+  // The resulting binary must be deemed interesting according to the shrinker's
+  // interestingness function.
+  return shrinker_interestingness_function_(
+      binary_out, counter_for_shrinker_interestingness_function);
+}
+
+void AddedFunctionReducer::ReplayPrefixAndAddFunction(
+    std::vector<uint32_t>* binary_out,
+    std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const {
+  assert(transformation_sequence_in_
+             .transformation(index_of_add_function_transformation_)
+             .has_add_function() &&
+         "A TransformationAddFunction is required at the given index.");
+
+  auto replay_result = Replayer(target_env_, consumer_, binary_in_,
+                                initial_facts_, transformation_sequence_in_,
+                                index_of_add_function_transformation_,
+                                validate_during_replay_, validator_options_)
+                           .Run();
+  assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
+         "Replay should succeed");
+  assert(static_cast<uint32_t>(
+             replay_result.applied_transformations.transformation_size()) ==
+             index_of_add_function_transformation_ &&
+         "All requested transformations should have applied.");
+
+  auto* ir_context = replay_result.transformed_module.get();
+
+  for (auto& type_or_value : ir_context->module()->types_values()) {
+    if (type_or_value.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (replay_result.transformation_context->GetFactManager()
+            ->PointeeValueIsIrrelevant(type_or_value.result_id())) {
+      irrelevant_pointee_global_variables->insert(type_or_value.result_id());
+    }
+  }
+
+  // Add the function associated with the transformation at
+  // |index_of_add_function_transformation| to the module.  By construction this
+  // should succeed.
+  const protobufs::TransformationAddFunction&
+      transformation_add_function_message =
+          transformation_sequence_in_
+              .transformation(index_of_add_function_transformation_)
+              .add_function();
+  bool success = TransformationAddFunction(transformation_add_function_message)
+                     .TryToAddFunction(ir_context);
+  (void)success;  // Keep release mode compilers happy.
+  assert(success && "Addition of the function should have succeeded.");
+
+  // Get the binary representation of the module with this function added.
+  ir_context->module()->ToBinary(binary_out, false);
+}
+
+void AddedFunctionReducer::ReplayAdaptedTransformations(
+    const std::vector<uint32_t>& binary_under_reduction,
+    std::vector<uint32_t>* binary_out,
+    protobufs::TransformationSequence* transformation_sequence_out) const {
+  assert(index_of_add_function_transformation_ <
+             static_cast<uint32_t>(
+                 transformation_sequence_in_.transformation_size()) &&
+         "The relevant add function transformation must be present.");
+  std::unique_ptr<opt::IRContext> ir_context_under_reduction =
+      BuildModule(target_env_, consumer_, binary_under_reduction.data(),
+                  binary_under_reduction.size());
+  assert(ir_context_under_reduction && "Error building module.");
+
+  protobufs::TransformationSequence modified_transformations;
+  for (uint32_t i = 0;
+       i <
+       static_cast<uint32_t>(transformation_sequence_in_.transformation_size());
+       i++) {
+    if (i == index_of_add_function_transformation_) {
+      protobufs::TransformationAddFunction modified_add_function =
+          transformation_sequence_in_
+              .transformation(index_of_add_function_transformation_)
+              .add_function();
+      assert(GetAddedFunctionId() ==
+                 modified_add_function.instruction(0).result_id() &&
+             "Unexpected result id for added function.");
+      modified_add_function.clear_instruction();
+      for (auto& function : *ir_context_under_reduction->module()) {
+        if (function.result_id() != GetAddedFunctionId()) {
+          continue;
+        }
+        function.ForEachInst(
+            [&modified_add_function](const opt::Instruction* instruction) {
+              *modified_add_function.add_instruction() =
+                  MakeInstructionMessage(instruction);
+            });
+      }
+      assert(modified_add_function.instruction_size() > 0 &&
+             "Some instructions for the added function should remain.");
+      *modified_transformations.add_transformation()->mutable_add_function() =
+          modified_add_function;
+    } else {
+      *modified_transformations.add_transformation() =
+          transformation_sequence_in_.transformation(i);
+    }
+  }
+  assert(
+      transformation_sequence_in_.transformation_size() ==
+          modified_transformations.transformation_size() &&
+      "The original and modified transformations should have the same size.");
+  auto replay_result = Replayer(target_env_, consumer_, binary_in_,
+                                initial_facts_, modified_transformations,
+                                modified_transformations.transformation_size(),
+                                validate_during_replay_, validator_options_)
+                           .Run();
+  assert(replay_result.status == Replayer::ReplayerResultStatus::kComplete &&
+         "Replay should succeed.");
+  replay_result.transformed_module->module()->ToBinary(binary_out, false);
+  *transformation_sequence_out =
+      std::move(replay_result.applied_transformations);
+}
+
+uint32_t AddedFunctionReducer::GetAddedFunctionId() const {
+  return transformation_sequence_in_
+      .transformation(index_of_add_function_transformation_)
+      .add_function()
+      .instruction(0)
+      .result_id();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/added_function_reducer.h b/source/fuzz/added_function_reducer.h
new file mode 100644
index 0000000..3efd268
--- /dev/null
+++ b/source/fuzz/added_function_reducer.h
@@ -0,0 +1,193 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_ADDED_FUNCTION_REDUCER_H_
+#define SOURCE_FUZZ_ADDED_FUNCTION_REDUCER_H_
+
+#include <unordered_set>
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/shrinker.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace fuzz {
+
+// An auxiliary class used by Shrinker, this class takes care of using
+// spirv-reduce to reduce the body of a function encoded in an AddFunction
+// transformation, in case a smaller, simpler function can be added instead.
+class AddedFunctionReducer {
+ public:
+  // Possible statuses that can result from running the shrinker.
+  enum class AddedFunctionReducerResultStatus {
+    kComplete,
+    kReductionFailed,
+  };
+
+  struct AddedFunctionReducerResult {
+    AddedFunctionReducerResultStatus status;
+    std::vector<uint32_t> transformed_binary;
+    protobufs::TransformationSequence applied_transformations;
+    uint32_t num_reduction_attempts;
+  };
+
+  AddedFunctionReducer(
+      spv_target_env target_env, MessageConsumer consumer,
+      const std::vector<uint32_t>& binary_in,
+      const protobufs::FactSequence& initial_facts,
+      const protobufs::TransformationSequence& transformation_sequence_in,
+      uint32_t index_of_add_function_transformation,
+      const Shrinker::InterestingnessFunction&
+          shrinker_interestingness_function,
+      bool validate_during_replay, spv_validator_options validator_options,
+      uint32_t shrinker_step_limit, uint32_t num_existing_shrink_attempts);
+
+  // Disables copy/move constructor/assignment operations.
+  AddedFunctionReducer(const AddedFunctionReducer&) = delete;
+  AddedFunctionReducer(AddedFunctionReducer&&) = delete;
+  AddedFunctionReducer& operator=(const AddedFunctionReducer&) = delete;
+  AddedFunctionReducer& operator=(AddedFunctionReducer&&) = delete;
+
+  ~AddedFunctionReducer();
+
+  // Invokes spirv-reduce on the function in the AddFunction transformation
+  // identified by |index_of_add_function_transformation|.  Returns a sequence
+  // of transformations identical to |transformation_sequence_in|, except that
+  // the AddFunction transformation at |index_of_add_function_transformation|
+  // might have been simplified.  The binary associated with applying the
+  // resulting sequence of transformations to |binary_in| is also returned, as
+  // well as the number of reduction steps that spirv-reduce made.
+  //
+  // On failure, an empty transformation sequence and binary are returned,
+  // with a placeholder value of 0 for the number of reduction attempts.
+  AddedFunctionReducerResult Run();
+
+ private:
+  // Yields, via |binary_out|, the binary obtained by applying transformations
+  // [0, |index_of_added_function_| - 1] from |transformations_in_| to
+  // |binary_in_|, and then adding the raw function encoded in
+  // |transformations_in_[index_of_added_function_]| (without adapting that
+  // function to make it livesafe).  This function has |added_function_id_| as
+  // its result id.
+  //
+  // The ids associated with all global variables in |binary_out| that had the
+  // "irrelevant pointee value" fact are also returned via
+  // |irrelevant_pointee_global_variables|.
+  //
+  // The point of this function is that spirv-reduce can subsequently be applied
+  // to function |added_function_id_| in |binary_out|.  By construction,
+  // |added_function_id_| should originally manipulate globals for which
+  // "irrelevant pointee value" facts hold.  The set
+  // |irrelevant_pointee_global_variables| can be used to force spirv-reduce
+  // to preserve this, to avoid the reduced function ending up manipulating
+  // other global variables of the SPIR-V module, potentially changing their
+  // value and thus changing the semantics of the module.
+  void ReplayPrefixAndAddFunction(
+      std::vector<uint32_t>* binary_out,
+      std::unordered_set<uint32_t>* irrelevant_pointee_global_variables) const;
+
+  // This is the interestingness function that will be used by spirv-reduce
+  // when shrinking the added function.
+  //
+  // For |binary_under_reduction| to be deemed interesting, the following
+  // conditions must hold:
+  // - The function with id |added_function_id_| in |binary_under_reduction|
+  //   must only reference global variables in
+  //   |irrelevant_pointee_global_variables|.  This avoids the reduced function
+  //   changing the semantics of the original SPIR-V module.
+  // - It must be possible to successfully replay the transformations in
+  //   |transformation_sequence_in_|, adapted so that the function added by the
+  //   transformation at |index_of_add_function_transformation_| is replaced by
+  //   the function with id |added_function_id_| in |binary_under_reduction|,
+  //   to |binary_in| (starting with initial facts |initial_facts_|).
+  // - All the transformations in this sequence must be successfully applied
+  //   during replay.
+  // - The resulting binary must be interesting according to
+  //   |shrinker_interestingness_function_|.
+  bool InterestingnessFunctionForReducingAddedFunction(
+      const std::vector<uint32_t>& binary_under_reduction,
+      const std::unordered_set<uint32_t>& irrelevant_pointee_global_variables);
+
+  // Starting with |binary_in_| and |initial_facts_|, the transformations in
+  // |transformation_sequence_in_| are replayed.  However, the transformation
+  // at index |index_of_add_function_transformation_| of
+  // |transformation_sequence_in_| -- which is guaranteed to be an AddFunction
+  // transformation -- is adapted so that the function to be added is replaced
+  // with the function in |binary_under_reduction| with id |added_function_id_|.
+  //
+  // The binary resulting from this replay is returned via |binary_out|, and the
+  // adapted transformation sequence via |transformation_sequence_out|.
+  void ReplayAdaptedTransformations(
+      const std::vector<uint32_t>& binary_under_reduction,
+      std::vector<uint32_t>* binary_out,
+      protobufs::TransformationSequence* transformation_sequence_out) const;
+
+  // Returns the id of the function to be added by the AddFunction
+  // transformation at
+  // |transformation_sequence_in_[index_of_add_function_transformation_]|.
+  uint32_t GetAddedFunctionId() const;
+
+  // Target environment.
+  const spv_target_env target_env_;
+
+  // Message consumer.
+  MessageConsumer consumer_;
+
+  // The initial binary to which transformations are applied -- i.e., the
+  // binary to which spirv-fuzz originally applied transformations.
+  const std::vector<uint32_t>& binary_in_;
+
+  // Initial facts about |binary_in_|.
+  const protobufs::FactSequence& initial_facts_;
+
+  // A set of transformations that can be successfully applied to |binary_in_|.
+  const protobufs::TransformationSequence& transformation_sequence_in_;
+
+  // An index into |transformation_sequence_in_| referring to an AddFunction
+  // transformation.  This is the transformation to be simplified using
+  // spirv-reduce.
+  const uint32_t index_of_add_function_transformation_;
+
+  // The interestingness function that has been provided to guide the
+  // overall shrinking process.  The AddFunction transformation being simplified
+  // by this class should still -- when applied in conjunction with the other
+  // transformations in |transformation_sequence_in_| -- lead to a binary that
+  // is deemed interesting by this function.
+  const Shrinker::InterestingnessFunction& shrinker_interestingness_function_;
+
+  // Determines whether to check for validity during the replaying of
+  // transformations.
+  const bool validate_during_replay_;
+
+  // Options to control validation.
+  spv_validator_options validator_options_;
+
+  // The step limit associated with the overall shrinking process.
+  const uint32_t shrinker_step_limit_;
+
+  // The number of shrink attempts that had been applied prior to invoking this
+  // AddedFunctionReducer instance.
+  const uint32_t num_existing_shrink_attempts_;
+
+  // Tracks the number of attempts that spirv-reduce has invoked its
+  // interestingness function, which it does once at the start of reduction,
+  // and then once more each time it makes a reduction step.
+  uint32_t num_reducer_interestingness_function_invocations_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_ADDED_FUNCTION_REDUCER_H_
diff --git a/source/fuzz/call_graph.cpp b/source/fuzz/call_graph.cpp
index 15416fe..c52bc34 100644
--- a/source/fuzz/call_graph.cpp
+++ b/source/fuzz/call_graph.cpp
@@ -20,12 +20,32 @@
 namespace fuzz {
 
 CallGraph::CallGraph(opt::IRContext* context) {
-  // Initialize function in-degree and call graph edges to 0 and empty.
+  // Initialize function in-degree, call graph edges and corresponding maximum
+  // loop nesting depth to 0, empty and 0 respectively.
   for (auto& function : *context->module()) {
     function_in_degree_[function.result_id()] = 0;
     call_graph_edges_[function.result_id()] = std::set<uint32_t>();
+    function_max_loop_nesting_depth_[function.result_id()] = 0;
   }
 
+  // Record the maximum loop nesting depth for each edge, by keeping a map from
+  // pairs of function ids, where (A, B) represents a function call from A to B,
+  // to the corresponding maximum depth.
+  std::map<std::pair<uint32_t, uint32_t>, uint32_t> call_to_max_depth;
+
+  // Compute |function_in_degree_|, |call_graph_edges_| and |call_to_max_depth|.
+  BuildGraphAndGetDepthOfFunctionCalls(context, &call_to_max_depth);
+
+  // Compute |functions_in_topological_order_|.
+  ComputeTopologicalOrderOfFunctions();
+
+  // Compute |function_max_loop_nesting_depth_|.
+  ComputeInterproceduralFunctionCallDepths(call_to_max_depth);
+}
+
+void CallGraph::BuildGraphAndGetDepthOfFunctionCalls(
+    opt::IRContext* context,
+    std::map<std::pair<uint32_t, uint32_t>, uint32_t>* call_to_max_depth) {
   // Consider every function.
   for (auto& function : *context->module()) {
     // Avoid considering the same callee of this function multiple times by
@@ -39,6 +59,25 @@
         }
         // Get the id of the function being called.
         uint32_t callee = instruction.GetSingleWordInOperand(0);
+
+        // Get the loop nesting depth of this function call.
+        uint32_t loop_nesting_depth =
+            context->GetStructuredCFGAnalysis()->LoopNestingDepth(block.id());
+        // If inside a loop header, consider the function call nested inside the
+        // loop headed by the block.
+        if (block.IsLoopHeader()) {
+          loop_nesting_depth++;
+        }
+
+        // Update the map if we have not seen this pair (caller, callee)
+        // before or if this function call is from a greater depth.
+        if (!known_callees.count(callee) ||
+            call_to_max_depth->at({function.result_id(), callee}) <
+                loop_nesting_depth) {
+          call_to_max_depth->insert(
+              {{function.result_id(), callee}, loop_nesting_depth});
+        }
+
         if (known_callees.count(callee)) {
           // We have already considered a call to this function - ignore it.
           continue;
@@ -53,6 +92,69 @@
   }
 }
 
+void CallGraph::ComputeTopologicalOrderOfFunctions() {
+  // This is an implementation of Kahn’s algorithm for topological sorting.
+
+  // Initialise |functions_in_topological_order_|.
+  functions_in_topological_order_.clear();
+
+  // Get a copy of the initial in-degrees of all functions.  The algorithm
+  // involves decrementing these values, hence why we work on a copy.
+  std::map<uint32_t, uint32_t> function_in_degree = GetFunctionInDegree();
+
+  // Populate a queue with all those function ids with in-degree zero.
+  std::queue<uint32_t> queue;
+  for (auto& entry : function_in_degree) {
+    if (entry.second == 0) {
+      queue.push(entry.first);
+    }
+  }
+
+  // Pop ids from the queue, adding them to the sorted order and decreasing the
+  // in-degrees of their successors.  A successor who's in-degree becomes zero
+  // gets added to the queue.
+  while (!queue.empty()) {
+    auto next = queue.front();
+    queue.pop();
+    functions_in_topological_order_.push_back(next);
+    for (auto successor : GetDirectCallees(next)) {
+      assert(function_in_degree.at(successor) > 0 &&
+             "The in-degree cannot be zero if the function is a successor.");
+      function_in_degree[successor] = function_in_degree.at(successor) - 1;
+      if (function_in_degree.at(successor) == 0) {
+        queue.push(successor);
+      }
+    }
+  }
+
+  assert(functions_in_topological_order_.size() == function_in_degree.size() &&
+         "Every function should appear in the sort.");
+
+  return;
+}
+
+void CallGraph::ComputeInterproceduralFunctionCallDepths(
+    const std::map<std::pair<uint32_t, uint32_t>, uint32_t>&
+        call_to_max_depth) {
+  // Find the maximum loop nesting depth that each function can be
+  // called from, by considering them in topological order.
+  for (uint32_t function_id : functions_in_topological_order_) {
+    const auto& callees = call_graph_edges_[function_id];
+
+    // For each callee, update its maximum loop nesting depth, if a call from
+    // |function_id| increases it.
+    for (uint32_t callee : callees) {
+      uint32_t max_depth_from_this_function =
+          function_max_loop_nesting_depth_[function_id] +
+          call_to_max_depth.at({function_id, callee});
+      if (function_max_loop_nesting_depth_[callee] <
+          max_depth_from_this_function) {
+        function_max_loop_nesting_depth_[callee] = max_depth_from_this_function;
+      }
+    }
+  }
+}
+
 void CallGraph::PushDirectCallees(uint32_t function_id,
                                   std::queue<uint32_t>* queue) const {
   for (auto callee : GetDirectCallees(function_id)) {
diff --git a/source/fuzz/call_graph.h b/source/fuzz/call_graph.h
index 14cd23b..840b1f1 100644
--- a/source/fuzz/call_graph.h
+++ b/source/fuzz/call_graph.h
@@ -24,6 +24,9 @@
 namespace fuzz {
 
 // Represents the acyclic call graph of a SPIR-V module.
+// The module is assumed to be recursion-free, so there are no cycles in the
+// graph. This class is immutable, so it will need to be recomputed if the
+// module changes.
 class CallGraph {
  public:
   // Creates a call graph corresponding to the given SPIR-V module.
@@ -43,7 +46,44 @@
   // invokes.
   std::set<uint32_t> GetIndirectCallees(uint32_t function_id) const;
 
+  // Returns the ids of all the functions in the graph in a topological order,
+  // in relation to the function calls, which are assumed to be recursion-free.
+  const std::vector<uint32_t>& GetFunctionsInTopologicalOrder() const {
+    return functions_in_topological_order_;
+  }
+
+  // Returns the maximum loop nesting depth from which |function_id| can be
+  // called. This is computed inter-procedurally (i.e. if main calls A from
+  // depth 2 and A calls B from depth 1, the result will be 3 for A).
+  // This is a static analysis, so it's not necessarily true that the depth
+  // returned can actually be reached at runtime.
+  uint32_t GetMaxCallNestingDepth(uint32_t function_id) const {
+    return function_max_loop_nesting_depth_.at(function_id);
+  }
+
  private:
+  // Computes |call_graph_edges_| and |function_in_degree_|. For each pair (A,
+  // B) of functions such that there is at least a function call from A to B,
+  // adds, to |call_to_max_depth|, a mapping from (A, B) to the maximum loop
+  // nesting depth (within A) of any such function call.
+  void BuildGraphAndGetDepthOfFunctionCalls(
+      opt::IRContext* context,
+      std::map<std::pair<uint32_t, uint32_t>, uint32_t>* call_to_max_depth);
+
+  // Computes a topological order of the functions in the graph, writing the
+  // result to |functions_in_topological_order_|. Assumes that the function
+  // calls are recursion-free and that |function_in_degree_| has been computed.
+  void ComputeTopologicalOrderOfFunctions();
+
+  // Computes |function_max_loop_nesting_depth_| so that each function is mapped
+  // to the maximum loop nesting depth from which it can be called, as described
+  // by the comment to GetMaxCallNestingDepth. Assumes that |call_graph_edges_|
+  // and |functions_in_topological_order_| have been computed, and that
+  // |call_to_max_depth| contains a mapping for each edge in the graph.
+  void ComputeInterproceduralFunctionCallDepths(
+      const std::map<std::pair<uint32_t, uint32_t>, uint32_t>&
+          call_to_max_depth);
+
   // Pushes the direct callees of |function_id| on to |queue|.
   void PushDirectCallees(uint32_t function_id,
                          std::queue<uint32_t>* queue) const;
@@ -54,6 +94,14 @@
   // For each function id, stores the number of distinct functions that call
   // the function.
   std::map<uint32_t, uint32_t> function_in_degree_;
+
+  // Stores the ids of the functions in a topological order,
+  // in relation to the function calls, which are assumed to be recursion-free.
+  std::vector<uint32_t> functions_in_topological_order_;
+
+  // For each function id, stores the maximum loop nesting depth that the
+  // function can be called from.
+  std::map<uint32_t, uint32_t> function_max_loop_nesting_depth_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/comparator_deep_blocks_first.h b/source/fuzz/comparator_deep_blocks_first.h
new file mode 100644
index 0000000..be63d1a
--- /dev/null
+++ b/source/fuzz/comparator_deep_blocks_first.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_COMPARATOR_BLOCKS_DEEP_FIRST_H_
+#define SOURCE_FUZZ_COMPARATOR_BLOCKS_DEEP_FIRST_H_
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Comparator for blocks, comparing them based on how deep they are nested
+// inside selection or loop constructs. Deeper blocks are considered less than
+// ones that are not as deep. The blocks are required to be in the same
+// function.
+class ComparatorDeepBlocksFirst {
+ public:
+  explicit ComparatorDeepBlocksFirst(opt::IRContext* ir_context)
+      : ir_context_(ir_context) {}
+
+  bool operator()(uint32_t bb1, uint32_t bb2) const {
+    return this->operator()(fuzzerutil::MaybeFindBlock(ir_context_, bb1),
+                            fuzzerutil::MaybeFindBlock(ir_context_, bb2));
+  }
+
+  bool operator()(const opt::BasicBlock* bb1, opt::BasicBlock* bb2) const {
+    assert(bb1 && bb2 && "The blocks must exist.");
+    assert(bb1->GetParent() == bb2->GetParent() &&
+           "The blocks must be in the same functions.");
+    return ir_context_->GetStructuredCFGAnalysis()->NestingDepth(bb1->id()) >
+           ir_context_->GetStructuredCFGAnalysis()->NestingDepth(bb2->id());
+  }
+
+ private:
+  opt::IRContext* ir_context_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_COMPARATOR_BLOCKS_DEEP_FIRST_H_
diff --git a/source/fuzz/counter_overflow_id_source.cpp b/source/fuzz/counter_overflow_id_source.cpp
new file mode 100644
index 0000000..0c21734
--- /dev/null
+++ b/source/fuzz/counter_overflow_id_source.cpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/counter_overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+CounterOverflowIdSource::CounterOverflowIdSource(uint32_t first_available_id)
+    : next_available_id_(first_available_id), issued_ids_() {}
+
+bool CounterOverflowIdSource::HasOverflowIds() const { return true; }
+
+uint32_t CounterOverflowIdSource::GetNextOverflowId() {
+  issued_ids_.insert(next_available_id_);
+  return next_available_id_++;
+}
+
+const std::unordered_set<uint32_t>&
+CounterOverflowIdSource::GetIssuedOverflowIds() const {
+  return issued_ids_;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/counter_overflow_id_source.h b/source/fuzz/counter_overflow_id_source.h
new file mode 100644
index 0000000..852bbd0
--- /dev/null
+++ b/source/fuzz/counter_overflow_id_source.h
@@ -0,0 +1,49 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_COUNTER_OVERFLOW_ID_SOURCE_H_
+#define SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_
+
+#include "source/fuzz/overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A source of overflow ids that uses a counter to provide successive ids from
+// a given starting value.
+class CounterOverflowIdSource : public OverflowIdSource {
+ public:
+  // |first_available_id| is the starting value for the counter.
+  explicit CounterOverflowIdSource(uint32_t first_available_id);
+
+  // Always returns true.
+  bool HasOverflowIds() const override;
+
+  // Returns the current counter value and increments the counter.
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) We should
+  //  account for the case where the maximum allowed id is reached.
+  uint32_t GetNextOverflowId() override;
+
+  const std::unordered_set<uint32_t>& GetIssuedOverflowIds() const override;
+
+ private:
+  uint32_t next_available_id_;
+
+  std::unordered_set<uint32_t> issued_ids_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_COUNTER_H_
diff --git a/source/fuzz/data_descriptor.cpp b/source/fuzz/data_descriptor.cpp
index 86e5325..c467363 100644
--- a/source/fuzz/data_descriptor.cpp
+++ b/source/fuzz/data_descriptor.cpp
@@ -19,8 +19,8 @@
 namespace spvtools {
 namespace fuzz {
 
-protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
-                                             std::vector<uint32_t>&& indices) {
+protobufs::DataDescriptor MakeDataDescriptor(
+    uint32_t object, const std::vector<uint32_t>& indices) {
   protobufs::DataDescriptor result;
   result.set_object(object);
   for (auto index : indices) {
diff --git a/source/fuzz/data_descriptor.h b/source/fuzz/data_descriptor.h
index c569ac8..f4b8e9c 100644
--- a/source/fuzz/data_descriptor.h
+++ b/source/fuzz/data_descriptor.h
@@ -15,18 +15,18 @@
 #ifndef SOURCE_FUZZ_DATA_DESCRIPTOR_H_
 #define SOURCE_FUZZ_DATA_DESCRIPTOR_H_
 
-#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
-
 #include <ostream>
 #include <vector>
 
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+
 namespace spvtools {
 namespace fuzz {
 
 // Factory method to create a data descriptor message from an object id and a
 // list of indices.
-protobufs::DataDescriptor MakeDataDescriptor(uint32_t object,
-                                             std::vector<uint32_t>&& indices);
+protobufs::DataDescriptor MakeDataDescriptor(
+    uint32_t object, const std::vector<uint32_t>& indices);
 
 // Hash function for data descriptors.
 struct DataDescriptorHash {
diff --git a/source/fuzz/equivalence_relation.h b/source/fuzz/equivalence_relation.h
index 6d0b63e..a01eac0 100644
--- a/source/fuzz/equivalence_relation.h
+++ b/source/fuzz/equivalence_relation.h
@@ -15,6 +15,8 @@
 #ifndef SOURCE_FUZZ_EQUIVALENCE_RELATION_H_
 #define SOURCE_FUZZ_EQUIVALENCE_RELATION_H_
 
+#include <algorithm>
+#include <cassert>
 #include <memory>
 #include <unordered_map>
 #include <unordered_set>
diff --git a/source/fuzz/fact_manager.cpp b/source/fuzz/fact_manager.cpp
deleted file mode 100644
index 6dff669..0000000
--- a/source/fuzz/fact_manager.cpp
+++ /dev/null
@@ -1,1563 +0,0 @@
-// Copyright (c) 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "source/fuzz/fact_manager.h"
-
-#include <sstream>
-#include <unordered_map>
-#include <unordered_set>
-
-#include "source/fuzz/equivalence_relation.h"
-#include "source/fuzz/fuzzer_util.h"
-#include "source/fuzz/uniform_buffer_element_descriptor.h"
-#include "source/opt/ir_context.h"
-
-namespace spvtools {
-namespace fuzz {
-
-namespace {
-
-std::string ToString(const protobufs::FactConstantUniform& fact) {
-  std::stringstream stream;
-  stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set()
-         << ", " << fact.uniform_buffer_element_descriptor().binding() << ")[";
-
-  bool first = true;
-  for (auto index : fact.uniform_buffer_element_descriptor().index()) {
-    if (first) {
-      first = false;
-    } else {
-      stream << ", ";
-    }
-    stream << index;
-  }
-
-  stream << "] == [";
-
-  first = true;
-  for (auto constant_word : fact.constant_word()) {
-    if (first) {
-      first = false;
-    } else {
-      stream << ", ";
-    }
-    stream << constant_word;
-  }
-
-  stream << "]";
-  return stream.str();
-}
-
-std::string ToString(const protobufs::FactDataSynonym& fact) {
-  std::stringstream stream;
-  stream << fact.data1() << " = " << fact.data2();
-  return stream.str();
-}
-
-std::string ToString(const protobufs::FactIdEquation& fact) {
-  std::stringstream stream;
-  stream << fact.lhs_id();
-  stream << " " << static_cast<SpvOp>(fact.opcode());
-  for (auto rhs_id : fact.rhs_id()) {
-    stream << " " << rhs_id;
-  }
-  return stream.str();
-}
-
-std::string ToString(const protobufs::Fact& fact) {
-  switch (fact.fact_case()) {
-    case protobufs::Fact::kConstantUniformFact:
-      return ToString(fact.constant_uniform_fact());
-    case protobufs::Fact::kDataSynonymFact:
-      return ToString(fact.data_synonym_fact());
-    case protobufs::Fact::kIdEquationFact:
-      return ToString(fact.id_equation_fact());
-    default:
-      assert(false && "Stringification not supported for this fact.");
-      return "";
-  }
-}
-
-}  // namespace
-
-//=======================
-// Constant uniform facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about uniform constants.
-class FactManager::ConstantUniformFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  bool AddFact(const protobufs::FactConstantUniform& fact,
-               opt::IRContext* context);
-
-  // See method in FactManager which delegates to this method.
-  std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
-      opt::IRContext* ir_context, uint32_t type_id) const;
-
-  // See method in FactManager which delegates to this method.
-  const std::vector<protobufs::UniformBufferElementDescriptor>
-  GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
-                                   uint32_t constant_id) const;
-
-  // See method in FactManager which delegates to this method.
-  uint32_t GetConstantFromUniformDescriptor(
-      opt::IRContext* context,
-      const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
-      const;
-
-  // See method in FactManager which delegates to this method.
-  std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
-
-  // See method in FactManager which delegates to this method.
-  const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
-  GetConstantUniformFactsAndTypes() const;
-
- private:
-  // Returns true if and only if the words associated with
-  // |constant_instruction| exactly match the words for the constant associated
-  // with |constant_uniform_fact|.
-  bool DataMatches(
-      const opt::Instruction& constant_instruction,
-      const protobufs::FactConstantUniform& constant_uniform_fact) const;
-
-  // Yields the constant words associated with |constant_uniform_fact|.
-  std::vector<uint32_t> GetConstantWords(
-      const protobufs::FactConstantUniform& constant_uniform_fact) const;
-
-  // Yields the id of a constant of type |type_id| whose data matches the
-  // constant data in |constant_uniform_fact|, or 0 if no such constant is
-  // declared.
-  uint32_t GetConstantId(
-      opt::IRContext* context,
-      const protobufs::FactConstantUniform& constant_uniform_fact,
-      uint32_t type_id) const;
-
-  // Checks that the width of a floating-point constant is supported, and that
-  // the constant is finite.
-  bool FloatingPointValueIsSuitable(const protobufs::FactConstantUniform& fact,
-                                    uint32_t width) const;
-
-  std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
-      facts_and_type_ids_;
-};
-
-uint32_t FactManager::ConstantUniformFacts::GetConstantId(
-    opt::IRContext* context,
-    const protobufs::FactConstantUniform& constant_uniform_fact,
-    uint32_t type_id) const {
-  auto type = context->get_type_mgr()->GetType(type_id);
-  assert(type != nullptr && "Unknown type id.");
-  const opt::analysis::Constant* known_constant;
-  if (type->AsInteger()) {
-    opt::analysis::IntConstant candidate_constant(
-        type->AsInteger(), GetConstantWords(constant_uniform_fact));
-    known_constant =
-        context->get_constant_mgr()->FindConstant(&candidate_constant);
-  } else {
-    assert(
-        type->AsFloat() &&
-        "Uniform constant facts are only supported for int and float types.");
-    opt::analysis::FloatConstant candidate_constant(
-        type->AsFloat(), GetConstantWords(constant_uniform_fact));
-    known_constant =
-        context->get_constant_mgr()->FindConstant(&candidate_constant);
-  }
-  if (!known_constant) {
-    return 0;
-  }
-  return context->get_constant_mgr()->FindDeclaredConstant(known_constant,
-                                                           type_id);
-}
-
-std::vector<uint32_t> FactManager::ConstantUniformFacts::GetConstantWords(
-    const protobufs::FactConstantUniform& constant_uniform_fact) const {
-  std::vector<uint32_t> result;
-  for (auto constant_word : constant_uniform_fact.constant_word()) {
-    result.push_back(constant_word);
-  }
-  return result;
-}
-
-bool FactManager::ConstantUniformFacts::DataMatches(
-    const opt::Instruction& constant_instruction,
-    const protobufs::FactConstantUniform& constant_uniform_fact) const {
-  assert(constant_instruction.opcode() == SpvOpConstant);
-  std::vector<uint32_t> data_in_constant;
-  for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) {
-    data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i));
-  }
-  return data_in_constant == GetConstantWords(constant_uniform_fact);
-}
-
-std::vector<uint32_t>
-FactManager::ConstantUniformFacts::GetConstantsAvailableFromUniformsForType(
-    opt::IRContext* ir_context, uint32_t type_id) const {
-  std::vector<uint32_t> result;
-  std::set<uint32_t> already_seen;
-  for (auto& fact_and_type_id : facts_and_type_ids_) {
-    if (fact_and_type_id.second != type_id) {
-      continue;
-    }
-    if (auto constant_id =
-            GetConstantId(ir_context, fact_and_type_id.first, type_id)) {
-      if (already_seen.find(constant_id) == already_seen.end()) {
-        result.push_back(constant_id);
-        already_seen.insert(constant_id);
-      }
-    }
-  }
-  return result;
-}
-
-const std::vector<protobufs::UniformBufferElementDescriptor>
-FactManager::ConstantUniformFacts::GetUniformDescriptorsForConstant(
-    opt::IRContext* ir_context, uint32_t constant_id) const {
-  std::vector<protobufs::UniformBufferElementDescriptor> result;
-  auto constant_inst = ir_context->get_def_use_mgr()->GetDef(constant_id);
-  assert(constant_inst->opcode() == SpvOpConstant &&
-         "The given id must be that of a constant");
-  auto type_id = constant_inst->type_id();
-  for (auto& fact_and_type_id : facts_and_type_ids_) {
-    if (fact_and_type_id.second != type_id) {
-      continue;
-    }
-    if (DataMatches(*constant_inst, fact_and_type_id.first)) {
-      result.emplace_back(
-          fact_and_type_id.first.uniform_buffer_element_descriptor());
-    }
-  }
-  return result;
-}
-
-uint32_t FactManager::ConstantUniformFacts::GetConstantFromUniformDescriptor(
-    opt::IRContext* context,
-    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
-  // Consider each fact.
-  for (auto& fact_and_type : facts_and_type_ids_) {
-    // Check whether the uniform descriptor associated with the fact matches
-    // |uniform_descriptor|.
-    if (UniformBufferElementDescriptorEquals()(
-            &uniform_descriptor,
-            &fact_and_type.first.uniform_buffer_element_descriptor())) {
-      return GetConstantId(context, fact_and_type.first, fact_and_type.second);
-    }
-  }
-  // No fact associated with the given uniform descriptor was found.
-  return 0;
-}
-
-std::vector<uint32_t>
-FactManager::ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown()
-    const {
-  std::vector<uint32_t> result;
-  for (auto& fact_and_type : facts_and_type_ids_) {
-    if (std::find(result.begin(), result.end(), fact_and_type.second) ==
-        result.end()) {
-      result.push_back(fact_and_type.second);
-    }
-  }
-  return result;
-}
-
-bool FactManager::ConstantUniformFacts::FloatingPointValueIsSuitable(
-    const protobufs::FactConstantUniform& fact, uint32_t width) const {
-  const uint32_t kFloatWidth = 32;
-  const uint32_t kDoubleWidth = 64;
-  if (width != kFloatWidth && width != kDoubleWidth) {
-    // Only 32- and 64-bit floating-point types are handled.
-    return false;
-  }
-  std::vector<uint32_t> words = GetConstantWords(fact);
-  if (width == 32) {
-    float value;
-    memcpy(&value, words.data(), sizeof(float));
-    if (!std::isfinite(value)) {
-      return false;
-    }
-  } else {
-    double value;
-    memcpy(&value, words.data(), sizeof(double));
-    if (!std::isfinite(value)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool FactManager::ConstantUniformFacts::AddFact(
-    const protobufs::FactConstantUniform& fact, opt::IRContext* context) {
-  // Try to find a unique instruction that declares a variable such that the
-  // variable is decorated with the descriptor set and binding associated with
-  // the constant uniform fact.
-  opt::Instruction* uniform_variable = FindUniformVariable(
-      fact.uniform_buffer_element_descriptor(), context, true);
-
-  if (!uniform_variable) {
-    return false;
-  }
-
-  assert(SpvOpVariable == uniform_variable->opcode());
-  assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0));
-
-  auto should_be_uniform_pointer_type =
-      context->get_type_mgr()->GetType(uniform_variable->type_id());
-  if (!should_be_uniform_pointer_type->AsPointer()) {
-    return false;
-  }
-  if (should_be_uniform_pointer_type->AsPointer()->storage_class() !=
-      SpvStorageClassUniform) {
-    return false;
-  }
-  auto should_be_uniform_pointer_instruction =
-      context->get_def_use_mgr()->GetDef(uniform_variable->type_id());
-  auto composite_type =
-      should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
-
-  auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices(
-      context, composite_type,
-      fact.uniform_buffer_element_descriptor().index());
-  if (!final_element_type_id) {
-    return false;
-  }
-  auto final_element_type =
-      context->get_type_mgr()->GetType(final_element_type_id);
-  assert(final_element_type &&
-         "There should be a type corresponding to this id.");
-
-  if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) {
-    return false;
-  }
-  auto width = final_element_type->AsFloat()
-                   ? final_element_type->AsFloat()->width()
-                   : final_element_type->AsInteger()->width();
-
-  if (final_element_type->AsFloat() &&
-      !FloatingPointValueIsSuitable(fact, width)) {
-    return false;
-  }
-
-  auto required_words = (width + 32 - 1) / 32;
-  if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
-    return false;
-  }
-  facts_and_type_ids_.emplace_back(
-      std::pair<protobufs::FactConstantUniform, uint32_t>(
-          fact, final_element_type_id));
-  return true;
-}
-
-const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
-FactManager::ConstantUniformFacts::GetConstantUniformFactsAndTypes() const {
-  return facts_and_type_ids_;
-}
-
-// End of uniform constant facts
-//==============================
-
-//==============================
-// Data synonym and id equation facts
-
-// This helper struct represents the right hand side of an equation as an
-// operator applied to a number of data descriptor operands.
-struct Operation {
-  SpvOp opcode;
-  std::vector<const protobufs::DataDescriptor*> operands;
-};
-
-// Hashing for operations, to allow deterministic unordered sets.
-struct OperationHash {
-  size_t operator()(const Operation& operation) const {
-    std::u32string hash;
-    hash.push_back(operation.opcode);
-    for (auto operand : operation.operands) {
-      hash.push_back(static_cast<uint32_t>(DataDescriptorHash()(operand)));
-    }
-    return std::hash<std::u32string>()(hash);
-  }
-};
-
-// Equality for operations, to allow deterministic unordered sets.
-struct OperationEquals {
-  bool operator()(const Operation& first, const Operation& second) const {
-    // Equal operations require...
-    //
-    // Equal opcodes.
-    if (first.opcode != second.opcode) {
-      return false;
-    }
-    // Matching operand counds.
-    if (first.operands.size() != second.operands.size()) {
-      return false;
-    }
-    // Equal operands.
-    for (uint32_t i = 0; i < first.operands.size(); i++) {
-      if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) {
-        return false;
-      }
-    }
-    return true;
-  }
-};
-
-// A helper, for debugging, to represent an operation as a string.
-std::string ToString(const Operation& operation) {
-  std::stringstream stream;
-  stream << operation.opcode;
-  for (auto operand : operation.operands) {
-    stream << " " << *operand;
-  }
-  return stream.str();
-}
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about data synonyms and id equations.
-class FactManager::DataSynonymAndIdEquationFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactDataSynonym& fact, opt::IRContext* context);
-
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactIdEquation& fact, opt::IRContext* context);
-
-  // See method in FactManager which delegates to this method.
-  std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
-      const protobufs::DataDescriptor& data_descriptor) const;
-
-  // See method in FactManager which delegates to this method.
-  std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
-
-  // See method in FactManager which delegates to this method.
-  bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
-                    const protobufs::DataDescriptor& data_descriptor2) const;
-
-  // See method in FactManager which delegates to this method.
-  void ComputeClosureOfFacts(opt::IRContext* context,
-                             uint32_t maximum_equivalence_class_size);
-
- private:
-  using OperationSet =
-      std::unordered_set<Operation, OperationHash, OperationEquals>;
-
-  // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses
-  // into sub-components of the data descriptors, if they are composites, to
-  // record that their components are pairwise-synonymous.
-  void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1,
-                                   const protobufs::DataDescriptor& dd2,
-                                   opt::IRContext* context);
-
-  // Computes various corollary facts from the data descriptor |dd| if members
-  // of its equivalence class participate in equation facts with OpConvert*
-  // opcodes. The descriptor should be registered in the equivalence relation.
-  void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd,
-                                         opt::IRContext* context);
-
-  // Recurses into sub-components of the data descriptors, if they are
-  // composites, to record that their components are pairwise-synonymous.
-  void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1,
-                                        const protobufs::DataDescriptor& dd2,
-                                        opt::IRContext* context);
-
-  // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets
-  // of equations that are known about them.
-  void MakeEquivalent(const protobufs::DataDescriptor& dd1,
-                      const protobufs::DataDescriptor& dd2);
-
-  // Returns true if and only if |dd1| and |dd2| are valid data descriptors
-  // whose associated data have the same type (modulo integer signedness).
-  bool DataDescriptorsAreWellFormedAndComparable(
-      opt::IRContext* context, const protobufs::DataDescriptor& dd1,
-      const protobufs::DataDescriptor& dd2) const;
-
-  OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const;
-
-  // Requires that |lhs_dd| and every element of |rhs_dds| is present in the
-  // |synonymous_| equivalence relation, but is not necessarily its own
-  // representative.  Records the fact that the equation
-  // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any
-  // corollaries, in the form of data synonym or equation facts, that follow
-  // from this and other known facts.
-  void AddEquationFactRecursive(
-      const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
-      const std::vector<const protobufs::DataDescriptor*>& rhs_dds,
-      opt::IRContext* context);
-
-  // The data descriptors that are known to be synonymous with one another are
-  // captured by this equivalence relation.
-  EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash,
-                      DataDescriptorEquals>
-      synonymous_;
-
-  // When a new synonym fact is added, it may be possible to deduce further
-  // synonym facts by computing a closure of all known facts.  However, this is
-  // an expensive operation, so it should be performed sparingly and only there
-  // is some chance of new facts being deduced.  This boolean tracks whether a
-  // closure computation is required - i.e., whether a new fact has been added
-  // since the last time such a computation was performed.
-  bool closure_computation_required_ = false;
-
-  // Represents a set of equations on data descriptors as a map indexed by
-  // left-hand-side, mapping a left-hand-side to a set of operations, each of
-  // which (together with the left-hand-side) defines an equation.
-  //
-  // All data descriptors occurring in equations are required to be present in
-  // the |synonymous_| equivalence relation, and to be their own representatives
-  // in that relation.
-  std::unordered_map<const protobufs::DataDescriptor*, OperationSet>
-      id_equations_;
-};
-
-void FactManager::DataSynonymAndIdEquationFacts::AddFact(
-    const protobufs::FactDataSynonym& fact, opt::IRContext* context) {
-  // Add the fact, including all facts relating sub-components of the data
-  // descriptors that are involved.
-  AddDataSynonymFactRecursive(fact.data1(), fact.data2(), context);
-}
-
-void FactManager::DataSynonymAndIdEquationFacts::AddFact(
-    const protobufs::FactIdEquation& fact, opt::IRContext* context) {
-  protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {});
-
-  // Register the LHS in the equivalence relation if needed.
-  if (!synonymous_.Exists(lhs_dd)) {
-    synonymous_.Register(lhs_dd);
-  }
-
-  // Get equivalence class representatives for all ids used on the RHS of the
-  // equation.
-  std::vector<const protobufs::DataDescriptor*> rhs_dd_ptrs;
-  for (auto rhs_id : fact.rhs_id()) {
-    // Register a data descriptor based on this id in the equivalence relation
-    // if needed, and then record the equivalence class representative.
-    protobufs::DataDescriptor rhs_dd = MakeDataDescriptor(rhs_id, {});
-    if (!synonymous_.Exists(rhs_dd)) {
-      synonymous_.Register(rhs_dd);
-    }
-    rhs_dd_ptrs.push_back(synonymous_.Find(&rhs_dd));
-  }
-
-  // Now add the fact.
-  AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()),
-                           rhs_dd_ptrs, context);
-}
-
-FactManager::DataSynonymAndIdEquationFacts::OperationSet
-FactManager::DataSynonymAndIdEquationFacts::GetEquations(
-    const protobufs::DataDescriptor* lhs) const {
-  auto existing = id_equations_.find(lhs);
-  if (existing == id_equations_.end()) {
-    return OperationSet();
-  }
-  return existing->second;
-}
-
-void FactManager::DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
-    const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
-    const std::vector<const protobufs::DataDescriptor*>& rhs_dds,
-    opt::IRContext* context) {
-  assert(synonymous_.Exists(lhs_dd) &&
-         "The LHS must be known to the equivalence relation.");
-  for (auto rhs_dd : rhs_dds) {
-    // Keep release compilers happy.
-    (void)(rhs_dd);
-    assert(synonymous_.Exists(*rhs_dd) &&
-           "The RHS operands must be known to the equivalence relation.");
-  }
-
-  auto lhs_dd_representative = synonymous_.Find(&lhs_dd);
-
-  if (id_equations_.count(lhs_dd_representative) == 0) {
-    // We have not seen an equation with this LHS before, so associate the LHS
-    // with an initially empty set.
-    id_equations_.insert({lhs_dd_representative, OperationSet()});
-  }
-
-  {
-    auto existing_equations = id_equations_.find(lhs_dd_representative);
-    assert(existing_equations != id_equations_.end() &&
-           "A set of operations should be present, even if empty.");
-
-    Operation new_operation = {opcode, rhs_dds};
-    if (existing_equations->second.count(new_operation)) {
-      // This equation is known, so there is nothing further to be done.
-      return;
-    }
-    // Add the equation to the set of known equations.
-    existing_equations->second.insert(new_operation);
-  }
-
-  // Now try to work out corollaries implied by the new equation and existing
-  // facts.
-  switch (opcode) {
-    case SpvOpConvertSToF:
-    case SpvOpConvertUToF:
-      ComputeConversionDataSynonymFacts(*rhs_dds[0], context);
-      break;
-    case SpvOpIAdd: {
-      // Equation form: "a = b + c"
-      for (const auto& equation : GetEquations(rhs_dds[0])) {
-        if (equation.opcode == SpvOpISub) {
-          // Equation form: "a = (d - e) + c"
-          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) {
-            // Equation form: "a = (d - c) + c"
-            // We can thus infer "a = d"
-            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
-          }
-          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
-            // Equation form: "a = (c - e) + c"
-            // We can thus infer "a = -e"
-            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
-                                     {equation.operands[1]}, context);
-          }
-        }
-      }
-      for (const auto& equation : GetEquations(rhs_dds[1])) {
-        if (equation.opcode == SpvOpISub) {
-          // Equation form: "a = b + (d - e)"
-          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) {
-            // Equation form: "a = b + (d - b)"
-            // We can thus infer "a = d"
-            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
-          }
-        }
-      }
-      break;
-    }
-    case SpvOpISub: {
-      // Equation form: "a = b - c"
-      for (const auto& equation : GetEquations(rhs_dds[0])) {
-        if (equation.opcode == SpvOpIAdd) {
-          // Equation form: "a = (d + e) - c"
-          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
-            // Equation form: "a = (c + e) - c"
-            // We can thus infer "a = e"
-            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context);
-          }
-          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) {
-            // Equation form: "a = (d + c) - c"
-            // We can thus infer "a = d"
-            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
-          }
-        }
-
-        if (equation.opcode == SpvOpISub) {
-          // Equation form: "a = (d - e) - c"
-          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
-            // Equation form: "a = (c - e) - c"
-            // We can thus infer "a = -e"
-            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
-                                     {equation.operands[1]}, context);
-          }
-        }
-      }
-
-      for (const auto& equation : GetEquations(rhs_dds[1])) {
-        if (equation.opcode == SpvOpIAdd) {
-          // Equation form: "a = b - (d + e)"
-          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) {
-            // Equation form: "a = b - (b + e)"
-            // We can thus infer "a = -e"
-            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
-                                     {equation.operands[1]}, context);
-          }
-          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) {
-            // Equation form: "a = b - (d + b)"
-            // We can thus infer "a = -d"
-            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
-                                     {equation.operands[0]}, context);
-          }
-        }
-        if (equation.opcode == SpvOpISub) {
-          // Equation form: "a = b - (d - e)"
-          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) {
-            // Equation form: "a = b - (b - e)"
-            // We can thus infer "a = e"
-            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1], context);
-          }
-        }
-      }
-      break;
-    }
-    case SpvOpLogicalNot:
-    case SpvOpSNegate: {
-      // Equation form: "a = !b" or "a = -b"
-      for (const auto& equation : GetEquations(rhs_dds[0])) {
-        if (equation.opcode == opcode) {
-          // Equation form: "a = !!b" or "a = -(-b)"
-          // We can thus infer "a = b"
-          AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0], context);
-        }
-      }
-      break;
-    }
-    default:
-      break;
-  }
-}
-
-void FactManager::DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
-    const protobufs::DataDescriptor& dd1, const protobufs::DataDescriptor& dd2,
-    opt::IRContext* context) {
-  assert(DataDescriptorsAreWellFormedAndComparable(context, dd1, dd2));
-
-  // Record that the data descriptors provided in the fact are equivalent.
-  MakeEquivalent(dd1, dd2);
-
-  // Compute various corollary facts.
-  ComputeConversionDataSynonymFacts(dd1, context);
-  ComputeCompositeDataSynonymFacts(dd1, dd2, context);
-}
-
-void FactManager::DataSynonymAndIdEquationFacts::
-    ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd,
-                                      opt::IRContext* context) {
-  assert(synonymous_.Exists(dd) &&
-         "|dd| should've been registered in the equivalence relation");
-
-  const auto* representative = synonymous_.Find(&dd);
-  assert(representative &&
-         "Representative can't be null for a registered descriptor");
-
-  const auto* type =
-      context->get_type_mgr()->GetType(fuzzerutil::WalkCompositeTypeIndices(
-          context, fuzzerutil::GetTypeId(context, representative->object()),
-          representative->index()));
-  assert(type && "Data descriptor has invalid type");
-
-  if ((type->AsVector() && type->AsVector()->element_type()->AsInteger()) ||
-      type->AsInteger()) {
-    // If there exist equation facts of the form |%a = opcode %representative|
-    // and |%b = opcode %representative| where |opcode| is either OpConvertSToF
-    // or OpConvertUToF, then |a| and |b| are synonymous.
-    std::vector<const protobufs::DataDescriptor*> convert_s_to_f_lhs;
-    std::vector<const protobufs::DataDescriptor*> convert_u_to_f_lhs;
-
-    for (const auto& fact : id_equations_) {
-      for (const auto& equation : fact.second) {
-        if (synonymous_.IsEquivalent(*equation.operands[0], *representative)) {
-          if (equation.opcode == SpvOpConvertSToF) {
-            convert_s_to_f_lhs.push_back(fact.first);
-          } else if (equation.opcode == SpvOpConvertUToF) {
-            convert_u_to_f_lhs.push_back(fact.first);
-          }
-        }
-      }
-    }
-
-    for (const auto& synonyms :
-         {std::move(convert_s_to_f_lhs), std::move(convert_u_to_f_lhs)}) {
-      for (const auto* synonym_a : synonyms) {
-        for (const auto* synonym_b : synonyms) {
-          if (!synonymous_.IsEquivalent(*synonym_a, *synonym_b) &&
-              DataDescriptorsAreWellFormedAndComparable(context, *synonym_a,
-                                                        *synonym_b)) {
-            // |synonym_a| and |synonym_b| have compatible types - they are
-            // synonymous.
-            AddDataSynonymFactRecursive(*synonym_a, *synonym_b, context);
-          }
-        }
-      }
-    }
-  }
-}
-
-void FactManager::DataSynonymAndIdEquationFacts::
-    ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1,
-                                     const protobufs::DataDescriptor& dd2,
-                                     opt::IRContext* context) {
-  // Check whether this is a synonym about composite objects.  If it is,
-  // we can recursively add synonym facts about their associated sub-components.
-
-  // Get the type of the object referred to by the first data descriptor in the
-  // synonym fact.
-  uint32_t type_id = fuzzerutil::WalkCompositeTypeIndices(
-      context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(),
-      dd1.index());
-  auto type = context->get_type_mgr()->GetType(type_id);
-  auto type_instruction = context->get_def_use_mgr()->GetDef(type_id);
-  assert(type != nullptr &&
-         "Invalid data synonym fact: one side has an unknown type.");
-
-  // Check whether the type is composite, recording the number of elements
-  // associated with the composite if so.
-  uint32_t num_composite_elements;
-  if (type->AsArray()) {
-    num_composite_elements =
-        fuzzerutil::GetArraySize(*type_instruction, context);
-  } else if (type->AsMatrix()) {
-    num_composite_elements = type->AsMatrix()->element_count();
-  } else if (type->AsStruct()) {
-    num_composite_elements =
-        fuzzerutil::GetNumberOfStructMembers(*type_instruction);
-  } else if (type->AsVector()) {
-    num_composite_elements = type->AsVector()->element_count();
-  } else {
-    // The type is not a composite, so return.
-    return;
-  }
-
-  // If the fact has the form:
-  //   obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
-  // then for each composite index i, we add a fact of the form:
-  //   obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
-  //
-  // However, to avoid adding a large number of synonym facts e.g. in the case
-  // of arrays, we bound the number of composite elements to which this is
-  // applied.  Nevertheless, we always add a synonym fact for the final
-  // components, as this may be an interesting edge case.
-
-  // The bound on the number of indices of the composite pair to note as being
-  // synonymous.
-  const uint32_t kCompositeElementBound = 10;
-
-  for (uint32_t i = 0; i < num_composite_elements;) {
-    std::vector<uint32_t> extended_indices1 =
-        fuzzerutil::RepeatedFieldToVector(dd1.index());
-    extended_indices1.push_back(i);
-    std::vector<uint32_t> extended_indices2 =
-        fuzzerutil::RepeatedFieldToVector(dd2.index());
-    extended_indices2.push_back(i);
-    AddDataSynonymFactRecursive(
-        MakeDataDescriptor(dd1.object(), std::move(extended_indices1)),
-        MakeDataDescriptor(dd2.object(), std::move(extended_indices2)),
-        context);
-
-    if (i < kCompositeElementBound - 1 || i == num_composite_elements - 1) {
-      // We have not reached the bound yet, or have already skipped ahead to the
-      // last element, so increment the loop counter as standard.
-      i++;
-    } else {
-      // We have reached the bound, so skip ahead to the last element.
-      assert(i == kCompositeElementBound - 1);
-      i = num_composite_elements - 1;
-    }
-  }
-}
-
-void FactManager::DataSynonymAndIdEquationFacts::ComputeClosureOfFacts(
-    opt::IRContext* context, uint32_t maximum_equivalence_class_size) {
-  // Suppose that obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n] are distinct
-  // data descriptors that describe objects of the same composite type, and that
-  // the composite type is comprised of k components.
-  //
-  // For example, if m is a mat4x4 and v a vec4, we might consider:
-  //   m[2]: describes the 2nd column of m, a vec4
-  //   v[]: describes all of v, a vec4
-  //
-  // Suppose that we know, for every 0 <= i < k, that the fact:
-  //   obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
-  // holds - i.e. that the children of the two data descriptors are synonymous.
-  //
-  // Then we can conclude that:
-  //   obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
-  // holds.
-  //
-  // For instance, if we have the facts:
-  //   m[2, 0] == v[0]
-  //   m[2, 1] == v[1]
-  //   m[2, 2] == v[2]
-  //   m[2, 3] == v[3]
-  // then we can conclude that:
-  //   m[2] == v.
-  //
-  // This method repeatedly searches the equivalence relation of data
-  // descriptors, deducing and adding such facts, until a pass over the
-  // relation leads to no further facts being deduced.
-
-  // The method relies on working with pairs of data descriptors, and in
-  // particular being able to hash and compare such pairs.
-
-  using DataDescriptorPair =
-      std::pair<protobufs::DataDescriptor, protobufs::DataDescriptor>;
-
-  struct DataDescriptorPairHash {
-    std::size_t operator()(const DataDescriptorPair& pair) const {
-      return DataDescriptorHash()(&pair.first) ^
-             DataDescriptorHash()(&pair.second);
-    }
-  };
-
-  struct DataDescriptorPairEquals {
-    bool operator()(const DataDescriptorPair& first,
-                    const DataDescriptorPair& second) const {
-      return (DataDescriptorEquals()(&first.first, &second.first) &&
-              DataDescriptorEquals()(&first.second, &second.second)) ||
-             (DataDescriptorEquals()(&first.first, &second.second) &&
-              DataDescriptorEquals()(&first.second, &second.first));
-    }
-  };
-
-  // This map records, for a given pair of composite data descriptors of the
-  // same type, all the indices at which the data descriptors are known to be
-  // synonymous.  A pair is a key to this map only if we have observed that
-  // the pair are synonymous at *some* index, but not at *all* indices.
-  // Once we find that a pair of data descriptors are equivalent at all indices
-  // we record the fact that they are synonymous and remove them from the map.
-  //
-  // Using the m and v example from above, initially the pair (m[2], v) would
-  // not be a key to the map.  If we find that m[2, 2] == v[2] holds, we would
-  // add an entry:
-  //   (m[2], v) -> [false, false, true, false]
-  // to record that they are synonymous at index 2.  If we then find that
-  // m[2, 0] == v[0] holds, we would update this entry to:
-  //   (m[2], v) -> [true, false, true, false]
-  // If we then find that m[2, 3] == v[3] holds, we would update this entry to:
-  //   (m[2], v) -> [true, false, true, true]
-  // Finally, if we then find that m[2, 1] == v[1] holds, which would make the
-  // boolean vector true at every index, we would add the fact:
-  //   m[2] == v
-  // to the equivalence relation and remove (m[2], v) from the map.
-  std::unordered_map<DataDescriptorPair, std::vector<bool>,
-                     DataDescriptorPairHash, DataDescriptorPairEquals>
-      candidate_composite_synonyms;
-
-  // We keep looking for new facts until we perform a complete pass over the
-  // equivalence relation without finding any new facts.
-  while (closure_computation_required_) {
-    // We have not found any new facts yet during this pass; we set this to
-    // 'true' if we do find a new fact.
-    closure_computation_required_ = false;
-
-    // Consider each class in the equivalence relation.
-    for (auto representative :
-         synonymous_.GetEquivalenceClassRepresentatives()) {
-      auto equivalence_class = synonymous_.GetEquivalenceClass(*representative);
-
-      if (equivalence_class.size() > maximum_equivalence_class_size) {
-        // This equivalence class is larger than the maximum size we are willing
-        // to consider, so we skip it.  This potentially leads to missed fact
-        // deductions, but avoids excessive runtime for closure computation.
-        continue;
-      }
-
-      // Consider every data descriptor in the equivalence class.
-      for (auto dd1_it = equivalence_class.begin();
-           dd1_it != equivalence_class.end(); ++dd1_it) {
-        // If this data descriptor has no indices then it does not have the form
-        // obj_1[a_1, ..., a_m, i], so move on.
-        auto dd1 = *dd1_it;
-        if (dd1->index_size() == 0) {
-          continue;
-        }
-
-        // Consider every other data descriptor later in the equivalence class
-        // (due to symmetry, there is no need to compare with previous data
-        // descriptors).
-        auto dd2_it = dd1_it;
-        for (++dd2_it; dd2_it != equivalence_class.end(); ++dd2_it) {
-          auto dd2 = *dd2_it;
-          // If this data descriptor has no indices then it does not have the
-          // form obj_2[b_1, ..., b_n, i], so move on.
-          if (dd2->index_size() == 0) {
-            continue;
-          }
-
-          // At this point we know that:
-          // - |dd1| has the form obj_1[a_1, ..., a_m, i]
-          // - |dd2| has the form obj_2[b_1, ..., b_n, j]
-          assert(dd1->index_size() > 0 && dd2->index_size() > 0 &&
-                 "Control should not reach here if either data descriptor has "
-                 "no indices.");
-
-          // We are only interested if i == j.
-          if (dd1->index(dd1->index_size() - 1) !=
-              dd2->index(dd2->index_size() - 1)) {
-            continue;
-          }
-
-          const uint32_t common_final_index = dd1->index(dd1->index_size() - 1);
-
-          // Make data descriptors |dd1_prefix| and |dd2_prefix| for
-          //   obj_1[a_1, ..., a_m]
-          // and
-          //   obj_2[b_1, ..., b_n]
-          // These are the two data descriptors we might be getting closer to
-          // deducing as being synonymous, due to knowing that they are
-          // synonymous when extended by a particular index.
-          protobufs::DataDescriptor dd1_prefix;
-          dd1_prefix.set_object(dd1->object());
-          for (uint32_t i = 0; i < static_cast<uint32_t>(dd1->index_size() - 1);
-               i++) {
-            dd1_prefix.add_index(dd1->index(i));
-          }
-          protobufs::DataDescriptor dd2_prefix;
-          dd2_prefix.set_object(dd2->object());
-          for (uint32_t i = 0; i < static_cast<uint32_t>(dd2->index_size() - 1);
-               i++) {
-            dd2_prefix.add_index(dd2->index(i));
-          }
-          assert(!DataDescriptorEquals()(&dd1_prefix, &dd2_prefix) &&
-                 "By construction these prefixes should be different.");
-
-          // If we already know that these prefixes are synonymous, move on.
-          if (synonymous_.Exists(dd1_prefix) &&
-              synonymous_.Exists(dd2_prefix) &&
-              synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) {
-            continue;
-          }
-
-          // Get the type of obj_1
-          auto dd1_root_type_id =
-              context->get_def_use_mgr()->GetDef(dd1->object())->type_id();
-          // Use this type, together with a_1, ..., a_m, to get the type of
-          // obj_1[a_1, ..., a_m].
-          auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
-              context, dd1_root_type_id, dd1_prefix.index());
-
-          // Similarly, get the type of obj_2 and use it to get the type of
-          // obj_2[b_1, ..., b_n].
-          auto dd2_root_type_id =
-              context->get_def_use_mgr()->GetDef(dd2->object())->type_id();
-          auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
-              context, dd2_root_type_id, dd2_prefix.index());
-
-          // If the types of dd1_prefix and dd2_prefix are not the same, they
-          // cannot be synonymous.
-          if (dd1_prefix_type != dd2_prefix_type) {
-            continue;
-          }
-
-          // At this point, we know we have synonymous data descriptors of the
-          // form:
-          //   obj_1[a_1, ..., a_m, i]
-          //   obj_2[b_1, ..., b_n, i]
-          // with the same last_index i, such that:
-          //   obj_1[a_1, ..., a_m]
-          // and
-          //   obj_2[b_1, ..., b_n]
-          // have the same type.
-
-          // Work out how many components there are in the (common) commposite
-          // type associated with obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n].
-          // This depends on whether the composite type is array, matrix, struct
-          // or vector.
-          uint32_t num_components_in_composite;
-          auto composite_type =
-              context->get_type_mgr()->GetType(dd1_prefix_type);
-          auto composite_type_instruction =
-              context->get_def_use_mgr()->GetDef(dd1_prefix_type);
-          if (composite_type->AsArray()) {
-            num_components_in_composite =
-                fuzzerutil::GetArraySize(*composite_type_instruction, context);
-            if (num_components_in_composite == 0) {
-              // This indicates that the array has an unknown size, in which
-              // case we cannot be sure we have matched all of its elements with
-              // synonymous elements of another array.
-              continue;
-            }
-          } else if (composite_type->AsMatrix()) {
-            num_components_in_composite =
-                composite_type->AsMatrix()->element_count();
-          } else if (composite_type->AsStruct()) {
-            num_components_in_composite = fuzzerutil::GetNumberOfStructMembers(
-                *composite_type_instruction);
-          } else {
-            assert(composite_type->AsVector());
-            num_components_in_composite =
-                composite_type->AsVector()->element_count();
-          }
-
-          // We are one step closer to being able to say that |dd1_prefix| and
-          // |dd2_prefix| are synonymous.
-          DataDescriptorPair candidate_composite_synonym(dd1_prefix,
-                                                         dd2_prefix);
-
-          // We look up what we already know about this pair.
-          auto existing_entry =
-              candidate_composite_synonyms.find(candidate_composite_synonym);
-
-          if (existing_entry == candidate_composite_synonyms.end()) {
-            // If this is the first time we have seen the pair, we make a vector
-            // of size |num_components_in_composite| that is 'true' at the
-            // common final index associated with |dd1| and |dd2|, and 'false'
-            // everywhere else, and register this vector as being associated
-            // with the pair.
-            std::vector<bool> entry;
-            for (uint32_t i = 0; i < num_components_in_composite; i++) {
-              entry.push_back(i == common_final_index);
-            }
-            candidate_composite_synonyms[candidate_composite_synonym] = entry;
-            existing_entry =
-                candidate_composite_synonyms.find(candidate_composite_synonym);
-          } else {
-            // We have seen this pair of data descriptors before, and we now
-            // know that they are synonymous at one further index, so we
-            // update the entry to record that.
-            existing_entry->second[common_final_index] = true;
-          }
-          assert(existing_entry != candidate_composite_synonyms.end());
-
-          // Check whether |dd1_prefix| and |dd2_prefix| are now known to match
-          // at every sub-component.
-          bool all_components_match = true;
-          for (uint32_t i = 0; i < num_components_in_composite; i++) {
-            if (!existing_entry->second[i]) {
-              all_components_match = false;
-              break;
-            }
-          }
-          if (all_components_match) {
-            // The two prefixes match on all sub-components, so we know that
-            // they are synonymous.  We add this fact *non-recursively*, as we
-            // have deduced that |dd1_prefix| and |dd2_prefix| are synonymous
-            // by observing that all their sub-components are already
-            // synonymous.
-            assert(DataDescriptorsAreWellFormedAndComparable(
-                context, dd1_prefix, dd2_prefix));
-            MakeEquivalent(dd1_prefix, dd2_prefix);
-            // Now that we know this pair of data descriptors are synonymous,
-            // there is no point recording how close they are to being
-            // synonymous.
-            candidate_composite_synonyms.erase(candidate_composite_synonym);
-          }
-        }
-      }
-    }
-  }
-}
-
-void FactManager::DataSynonymAndIdEquationFacts::MakeEquivalent(
-    const protobufs::DataDescriptor& dd1,
-    const protobufs::DataDescriptor& dd2) {
-  // Register the data descriptors if they are not already known to the
-  // equivalence relation.
-  for (const auto& dd : {dd1, dd2}) {
-    if (!synonymous_.Exists(dd)) {
-      synonymous_.Register(dd);
-    }
-  }
-
-  if (synonymous_.IsEquivalent(dd1, dd2)) {
-    // The data descriptors are already known to be equivalent, so there is
-    // nothing to do.
-    return;
-  }
-
-  // We must make the data descriptors equivalent, and also make sure any
-  // equation facts known about their representatives are merged.
-
-  // Record the original equivalence class representatives of the data
-  // descriptors.
-  auto dd1_original_representative = synonymous_.Find(&dd1);
-  auto dd2_original_representative = synonymous_.Find(&dd2);
-
-  // Make the data descriptors equivalent.
-  synonymous_.MakeEquivalent(dd1, dd2);
-  // As we have updated the equivalence relation, we might be able to deduce
-  // more facts by performing a closure computation, so we record that such a
-  // computation is required.
-  closure_computation_required_ = true;
-
-  // At this point, exactly one of |dd1_original_representative| and
-  // |dd2_original_representative| will be the representative of the combined
-  // equivalence class.  We work out which one of them is still the class
-  // representative and which one is no longer the class representative.
-
-  auto still_representative = synonymous_.Find(dd1_original_representative) ==
-                                      dd1_original_representative
-                                  ? dd1_original_representative
-                                  : dd2_original_representative;
-  auto no_longer_representative =
-      still_representative == dd1_original_representative
-          ? dd2_original_representative
-          : dd1_original_representative;
-
-  assert(no_longer_representative != still_representative &&
-         "The current and former representatives cannot be the same.");
-
-  // We now need to add all equations about |no_longer_representative| to the
-  // set of equations known about |still_representative|.
-
-  // Get the equations associated with |no_longer_representative|.
-  auto no_longer_representative_id_equations =
-      id_equations_.find(no_longer_representative);
-  if (no_longer_representative_id_equations != id_equations_.end()) {
-    // There are some equations to transfer.  There might not yet be any
-    // equations about |still_representative|; create an empty set of equations
-    // if this is the case.
-    if (!id_equations_.count(still_representative)) {
-      id_equations_.insert({still_representative, OperationSet()});
-    }
-    auto still_representative_id_equations =
-        id_equations_.find(still_representative);
-    assert(still_representative_id_equations != id_equations_.end() &&
-           "At this point there must be a set of equations.");
-    // Add all the equations known about |no_longer_representative| to the set
-    // of equations known about |still_representative|.
-    still_representative_id_equations->second.insert(
-        no_longer_representative_id_equations->second.begin(),
-        no_longer_representative_id_equations->second.end());
-  }
-  // Delete the no longer-relevant equations about |no_longer_representative|.
-  id_equations_.erase(no_longer_representative);
-}
-
-bool FactManager::DataSynonymAndIdEquationFacts::
-    DataDescriptorsAreWellFormedAndComparable(
-        opt::IRContext* context, const protobufs::DataDescriptor& dd1,
-        const protobufs::DataDescriptor& dd2) const {
-  auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
-      context, context->get_def_use_mgr()->GetDef(dd1.object())->type_id(),
-      dd1.index());
-  auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices(
-      context, context->get_def_use_mgr()->GetDef(dd2.object())->type_id(),
-      dd2.index());
-  // The end types of the data descriptors must exist.
-  if (end_type_id_1 == 0 || end_type_id_2 == 0) {
-    return false;
-  }
-  // If the end types are the same, the data descriptors are comparable.
-  if (end_type_id_1 == end_type_id_2) {
-    return true;
-  }
-  // Otherwise they are only comparable if they are integer scalars or integer
-  // vectors that differ only in signedness.
-
-  // Get both types.
-  const opt::analysis::Type* type_1 =
-      context->get_type_mgr()->GetType(end_type_id_1);
-  const opt::analysis::Type* type_2 =
-      context->get_type_mgr()->GetType(end_type_id_2);
-
-  // If the first type is a vector, check that the second type is a vector of
-  // the same width, and drill down to the vector element types.
-  if (type_1->AsVector()) {
-    if (!type_2->AsVector()) {
-      return false;
-    }
-    if (type_1->AsVector()->element_count() !=
-        type_2->AsVector()->element_count()) {
-      return false;
-    }
-    type_1 = type_1->AsVector()->element_type();
-    type_2 = type_2->AsVector()->element_type();
-  }
-  // Check that type_1 and type_2 are both integer types of the same bit-width
-  // (but with potentially different signedness).
-  auto integer_type_1 = type_1->AsInteger();
-  auto integer_type_2 = type_2->AsInteger();
-  return integer_type_1 && integer_type_2 &&
-         integer_type_1->width() == integer_type_2->width();
-}
-
-std::vector<const protobufs::DataDescriptor*>
-FactManager::DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor(
-    const protobufs::DataDescriptor& data_descriptor) const {
-  if (synonymous_.Exists(data_descriptor)) {
-    return synonymous_.GetEquivalenceClass(data_descriptor);
-  }
-  return std::vector<const protobufs::DataDescriptor*>();
-}
-
-std::vector<uint32_t>
-FactManager::DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown()
-    const {
-  std::vector<uint32_t> result;
-  for (auto& data_descriptor : synonymous_.GetAllKnownValues()) {
-    if (data_descriptor->index().empty()) {
-      result.push_back(data_descriptor->object());
-    }
-  }
-  return result;
-}
-
-bool FactManager::DataSynonymAndIdEquationFacts::IsSynonymous(
-    const protobufs::DataDescriptor& data_descriptor1,
-    const protobufs::DataDescriptor& data_descriptor2) const {
-  return synonymous_.Exists(data_descriptor1) &&
-         synonymous_.Exists(data_descriptor2) &&
-         synonymous_.IsEquivalent(data_descriptor1, data_descriptor2);
-}
-
-// End of data synonym facts
-//==============================
-
-//==============================
-// Dead block facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about data blocks.
-class FactManager::DeadBlockFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactBlockIsDead& fact);
-
-  // See method in FactManager which delegates to this method.
-  bool BlockIsDead(uint32_t block_id) const;
-
- private:
-  std::set<uint32_t> dead_block_ids_;
-};
-
-void FactManager::DeadBlockFacts::AddFact(
-    const protobufs::FactBlockIsDead& fact) {
-  dead_block_ids_.insert(fact.block_id());
-}
-
-bool FactManager::DeadBlockFacts::BlockIsDead(uint32_t block_id) const {
-  return dead_block_ids_.count(block_id) != 0;
-}
-
-// End of dead block facts
-//==============================
-
-//==============================
-// Livesafe function facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about livesafe functions.
-class FactManager::LivesafeFunctionFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactFunctionIsLivesafe& fact);
-
-  // See method in FactManager which delegates to this method.
-  bool FunctionIsLivesafe(uint32_t function_id) const;
-
- private:
-  std::set<uint32_t> livesafe_function_ids_;
-};
-
-void FactManager::LivesafeFunctionFacts::AddFact(
-    const protobufs::FactFunctionIsLivesafe& fact) {
-  livesafe_function_ids_.insert(fact.function_id());
-}
-
-bool FactManager::LivesafeFunctionFacts::FunctionIsLivesafe(
-    uint32_t function_id) const {
-  return livesafe_function_ids_.count(function_id) != 0;
-}
-
-// End of livesafe function facts
-//==============================
-
-//==============================
-// Irrelevant value facts
-
-// The purpose of this class is to group the fields and data used to represent
-// facts about various irrelevant values in the module.
-class FactManager::IrrelevantValueFacts {
- public:
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactPointeeValueIsIrrelevant& fact);
-
-  // See method in FactManager which delegates to this method.
-  void AddFact(const protobufs::FactIdIsIrrelevant& fact);
-
-  // See method in FactManager which delegates to this method.
-  bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
-
-  // See method in FactManager which delegates to this method.
-  bool IdIsIrrelevant(uint32_t pointer_id) const;
-
- private:
-  std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_;
-  std::unordered_set<uint32_t> irrelevant_ids_;
-};
-
-void FactManager::IrrelevantValueFacts::AddFact(
-    const protobufs::FactPointeeValueIsIrrelevant& fact) {
-  pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id());
-}
-
-void FactManager::IrrelevantValueFacts::AddFact(
-    const protobufs::FactIdIsIrrelevant& fact) {
-  irrelevant_ids_.insert(fact.result_id());
-}
-
-bool FactManager::IrrelevantValueFacts::PointeeValueIsIrrelevant(
-    uint32_t pointer_id) const {
-  return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0;
-}
-
-bool FactManager::IrrelevantValueFacts::IdIsIrrelevant(
-    uint32_t pointer_id) const {
-  return irrelevant_ids_.count(pointer_id) != 0;
-}
-
-// End of arbitrarily-valued variable facts
-//==============================
-
-FactManager::FactManager()
-    : uniform_constant_facts_(MakeUnique<ConstantUniformFacts>()),
-      data_synonym_and_id_equation_facts_(
-          MakeUnique<DataSynonymAndIdEquationFacts>()),
-      dead_block_facts_(MakeUnique<DeadBlockFacts>()),
-      livesafe_function_facts_(MakeUnique<LivesafeFunctionFacts>()),
-      irrelevant_value_facts_(MakeUnique<IrrelevantValueFacts>()) {}
-
-FactManager::~FactManager() = default;
-
-void FactManager::AddFacts(const MessageConsumer& message_consumer,
-                           const protobufs::FactSequence& initial_facts,
-                           opt::IRContext* context) {
-  for (auto& fact : initial_facts.fact()) {
-    if (!AddFact(fact, context)) {
-      message_consumer(
-          SPV_MSG_WARNING, nullptr, {},
-          ("Invalid fact " + ToString(fact) + " ignored.").c_str());
-    }
-  }
-}
-
-bool FactManager::AddFact(const fuzz::protobufs::Fact& fact,
-                          opt::IRContext* context) {
-  switch (fact.fact_case()) {
-    case protobufs::Fact::kConstantUniformFact:
-      return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(),
-                                              context);
-    case protobufs::Fact::kDataSynonymFact:
-      data_synonym_and_id_equation_facts_->AddFact(fact.data_synonym_fact(),
-                                                   context);
-      return true;
-    case protobufs::Fact::kBlockIsDeadFact:
-      dead_block_facts_->AddFact(fact.block_is_dead_fact());
-      return true;
-    case protobufs::Fact::kFunctionIsLivesafeFact:
-      livesafe_function_facts_->AddFact(fact.function_is_livesafe_fact());
-      return true;
-    default:
-      assert(false && "Unknown fact type.");
-      return false;
-  }
-}
-
-void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1,
-                                     const protobufs::DataDescriptor& data2,
-                                     opt::IRContext* context) {
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550):
-  //  assert that neither |data1| nor |data2| are irrelevant.
-  protobufs::FactDataSynonym fact;
-  *fact.mutable_data1() = data1;
-  *fact.mutable_data2() = data2;
-  data_synonym_and_id_equation_facts_->AddFact(fact, context);
-}
-
-std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
-    opt::IRContext* ir_context, uint32_t type_id) const {
-  return uniform_constant_facts_->GetConstantsAvailableFromUniformsForType(
-      ir_context, type_id);
-}
-
-const std::vector<protobufs::UniformBufferElementDescriptor>
-FactManager::GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
-                                              uint32_t constant_id) const {
-  return uniform_constant_facts_->GetUniformDescriptorsForConstant(ir_context,
-                                                                   constant_id);
-}
-
-uint32_t FactManager::GetConstantFromUniformDescriptor(
-    opt::IRContext* context,
-    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
-  return uniform_constant_facts_->GetConstantFromUniformDescriptor(
-      context, uniform_descriptor);
-}
-
-std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
-    const {
-  return uniform_constant_facts_->GetTypesForWhichUniformValuesAreKnown();
-}
-
-const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
-FactManager::GetConstantUniformFactsAndTypes() const {
-  return uniform_constant_facts_->GetConstantUniformFactsAndTypes();
-}
-
-std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const {
-  return data_synonym_and_id_equation_facts_->GetIdsForWhichSynonymsAreKnown();
-}
-
-std::vector<const protobufs::DataDescriptor*>
-FactManager::GetSynonymsForDataDescriptor(
-    const protobufs::DataDescriptor& data_descriptor) const {
-  return data_synonym_and_id_equation_facts_->GetSynonymsForDataDescriptor(
-      data_descriptor);
-}
-
-std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId(
-    uint32_t id) const {
-  return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {}));
-}
-
-bool FactManager::IsSynonymous(
-    const protobufs::DataDescriptor& data_descriptor1,
-    const protobufs::DataDescriptor& data_descriptor2) const {
-  return data_synonym_and_id_equation_facts_->IsSynonymous(data_descriptor1,
-                                                           data_descriptor2);
-}
-
-bool FactManager::BlockIsDead(uint32_t block_id) const {
-  return dead_block_facts_->BlockIsDead(block_id);
-}
-
-void FactManager::AddFactBlockIsDead(uint32_t block_id) {
-  protobufs::FactBlockIsDead fact;
-  fact.set_block_id(block_id);
-  dead_block_facts_->AddFact(fact);
-}
-
-bool FactManager::FunctionIsLivesafe(uint32_t function_id) const {
-  return livesafe_function_facts_->FunctionIsLivesafe(function_id);
-}
-
-void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) {
-  protobufs::FactFunctionIsLivesafe fact;
-  fact.set_function_id(function_id);
-  livesafe_function_facts_->AddFact(fact);
-}
-
-bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
-  return irrelevant_value_facts_->PointeeValueIsIrrelevant(pointer_id);
-}
-
-bool FactManager::IdIsIrrelevant(uint32_t result_id) const {
-  return irrelevant_value_facts_->IdIsIrrelevant(result_id);
-}
-
-void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) {
-  protobufs::FactPointeeValueIsIrrelevant fact;
-  fact.set_pointer_id(pointer_id);
-  irrelevant_value_facts_->AddFact(fact);
-}
-
-void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) {
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550):
-  //  assert that |result_id| is not a part of any DataSynonym fact.
-  protobufs::FactIdIsIrrelevant fact;
-  fact.set_result_id(result_id);
-  irrelevant_value_facts_->AddFact(fact);
-}
-
-void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
-                                    const std::vector<uint32_t>& rhs_id,
-                                    opt::IRContext* context) {
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3550):
-  //  assert that elements of |rhs_id| and |lhs_id| are not irrelevant.
-  protobufs::FactIdEquation fact;
-  fact.set_lhs_id(lhs_id);
-  fact.set_opcode(opcode);
-  for (auto an_rhs_id : rhs_id) {
-    fact.add_rhs_id(an_rhs_id);
-  }
-  data_synonym_and_id_equation_facts_->AddFact(fact, context);
-}
-
-void FactManager::ComputeClosureOfFacts(
-    opt::IRContext* ir_context, uint32_t maximum_equivalence_class_size) {
-  data_synonym_and_id_equation_facts_->ComputeClosureOfFacts(
-      ir_context, maximum_equivalence_class_size);
-}
-
-}  // namespace fuzz
-}  // namespace spvtools
diff --git a/source/fuzz/fact_manager/constant_uniform_facts.cpp b/source/fuzz/fact_manager/constant_uniform_facts.cpp
new file mode 100644
index 0000000..a629c0d
--- /dev/null
+++ b/source/fuzz/fact_manager/constant_uniform_facts.cpp
@@ -0,0 +1,235 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/constant_uniform_facts.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+ConstantUniformFacts::ConstantUniformFacts(opt::IRContext* ir_context)
+    : ir_context_(ir_context) {}
+
+uint32_t ConstantUniformFacts::GetConstantId(
+    const protobufs::FactConstantUniform& constant_uniform_fact,
+    uint32_t type_id) const {
+  auto type = ir_context_->get_type_mgr()->GetType(type_id);
+  assert(type != nullptr && "Unknown type id.");
+  const opt::analysis::Constant* known_constant;
+  if (type->AsInteger()) {
+    opt::analysis::IntConstant candidate_constant(
+        type->AsInteger(), GetConstantWords(constant_uniform_fact));
+    known_constant =
+        ir_context_->get_constant_mgr()->FindConstant(&candidate_constant);
+  } else {
+    assert(
+        type->AsFloat() &&
+        "Uniform constant facts are only supported for int and float types.");
+    opt::analysis::FloatConstant candidate_constant(
+        type->AsFloat(), GetConstantWords(constant_uniform_fact));
+    known_constant =
+        ir_context_->get_constant_mgr()->FindConstant(&candidate_constant);
+  }
+  if (!known_constant) {
+    return 0;
+  }
+  return ir_context_->get_constant_mgr()->FindDeclaredConstant(known_constant,
+                                                               type_id);
+}
+
+std::vector<uint32_t> ConstantUniformFacts::GetConstantWords(
+    const protobufs::FactConstantUniform& constant_uniform_fact) {
+  std::vector<uint32_t> result;
+  for (auto constant_word : constant_uniform_fact.constant_word()) {
+    result.push_back(constant_word);
+  }
+  return result;
+}
+
+bool ConstantUniformFacts::DataMatches(
+    const opt::Instruction& constant_instruction,
+    const protobufs::FactConstantUniform& constant_uniform_fact) {
+  assert(constant_instruction.opcode() == SpvOpConstant);
+  std::vector<uint32_t> data_in_constant;
+  for (uint32_t i = 0; i < constant_instruction.NumInOperands(); i++) {
+    data_in_constant.push_back(constant_instruction.GetSingleWordInOperand(i));
+  }
+  return data_in_constant == GetConstantWords(constant_uniform_fact);
+}
+
+std::vector<uint32_t>
+ConstantUniformFacts::GetConstantsAvailableFromUniformsForType(
+    uint32_t type_id) const {
+  std::vector<uint32_t> result;
+  std::set<uint32_t> already_seen;
+  for (auto& fact_and_type_id : facts_and_type_ids_) {
+    if (fact_and_type_id.second != type_id) {
+      continue;
+    }
+    if (auto constant_id = GetConstantId(fact_and_type_id.first, type_id)) {
+      if (already_seen.find(constant_id) == already_seen.end()) {
+        result.push_back(constant_id);
+        already_seen.insert(constant_id);
+      }
+    }
+  }
+  return result;
+}
+
+std::vector<protobufs::UniformBufferElementDescriptor>
+ConstantUniformFacts::GetUniformDescriptorsForConstant(
+    uint32_t constant_id) const {
+  std::vector<protobufs::UniformBufferElementDescriptor> result;
+  auto constant_inst = ir_context_->get_def_use_mgr()->GetDef(constant_id);
+  assert(constant_inst->opcode() == SpvOpConstant &&
+         "The given id must be that of a constant");
+  auto type_id = constant_inst->type_id();
+  for (auto& fact_and_type_id : facts_and_type_ids_) {
+    if (fact_and_type_id.second != type_id) {
+      continue;
+    }
+    if (DataMatches(*constant_inst, fact_and_type_id.first)) {
+      result.emplace_back(
+          fact_and_type_id.first.uniform_buffer_element_descriptor());
+    }
+  }
+  return result;
+}
+
+uint32_t ConstantUniformFacts::GetConstantFromUniformDescriptor(
+    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+  // Consider each fact.
+  for (auto& fact_and_type : facts_and_type_ids_) {
+    // Check whether the uniform descriptor associated with the fact matches
+    // |uniform_descriptor|.
+    if (UniformBufferElementDescriptorEquals()(
+            &uniform_descriptor,
+            &fact_and_type.first.uniform_buffer_element_descriptor())) {
+      return GetConstantId(fact_and_type.first, fact_and_type.second);
+    }
+  }
+  // No fact associated with the given uniform descriptor was found.
+  return 0;
+}
+
+std::vector<uint32_t>
+ConstantUniformFacts::GetTypesForWhichUniformValuesAreKnown() const {
+  std::vector<uint32_t> result;
+  for (auto& fact_and_type : facts_and_type_ids_) {
+    if (std::find(result.begin(), result.end(), fact_and_type.second) ==
+        result.end()) {
+      result.push_back(fact_and_type.second);
+    }
+  }
+  return result;
+}
+
+bool ConstantUniformFacts::FloatingPointValueIsSuitable(
+    const protobufs::FactConstantUniform& fact, uint32_t width) {
+  const uint32_t kFloatWidth = 32;
+  const uint32_t kDoubleWidth = 64;
+  if (width != kFloatWidth && width != kDoubleWidth) {
+    // Only 32- and 64-bit floating-point types are handled.
+    return false;
+  }
+  std::vector<uint32_t> words = GetConstantWords(fact);
+  if (width == 32) {
+    float value;
+    memcpy(&value, words.data(), sizeof(float));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  } else {
+    double value;
+    memcpy(&value, words.data(), sizeof(double));
+    if (!std::isfinite(value)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool ConstantUniformFacts::MaybeAddFact(
+    const protobufs::FactConstantUniform& fact) {
+  // Try to find a unique instruction that declares a variable such that the
+  // variable is decorated with the descriptor set and binding associated with
+  // the constant uniform fact.
+  opt::Instruction* uniform_variable = FindUniformVariable(
+      fact.uniform_buffer_element_descriptor(), ir_context_, true);
+
+  if (!uniform_variable) {
+    return false;
+  }
+
+  assert(SpvOpVariable == uniform_variable->opcode());
+  assert(SpvStorageClassUniform == uniform_variable->GetSingleWordInOperand(0));
+
+  auto should_be_uniform_pointer_type =
+      ir_context_->get_type_mgr()->GetType(uniform_variable->type_id());
+  if (!should_be_uniform_pointer_type->AsPointer()) {
+    return false;
+  }
+  if (should_be_uniform_pointer_type->AsPointer()->storage_class() !=
+      SpvStorageClassUniform) {
+    return false;
+  }
+  auto should_be_uniform_pointer_instruction =
+      ir_context_->get_def_use_mgr()->GetDef(uniform_variable->type_id());
+  auto composite_type =
+      should_be_uniform_pointer_instruction->GetSingleWordInOperand(1);
+
+  auto final_element_type_id = fuzzerutil::WalkCompositeTypeIndices(
+      ir_context_, composite_type,
+      fact.uniform_buffer_element_descriptor().index());
+  if (!final_element_type_id) {
+    return false;
+  }
+  auto final_element_type =
+      ir_context_->get_type_mgr()->GetType(final_element_type_id);
+  assert(final_element_type &&
+         "There should be a type corresponding to this id.");
+
+  if (!(final_element_type->AsFloat() || final_element_type->AsInteger())) {
+    return false;
+  }
+  auto width = final_element_type->AsFloat()
+                   ? final_element_type->AsFloat()->width()
+                   : final_element_type->AsInteger()->width();
+
+  if (final_element_type->AsFloat() &&
+      !FloatingPointValueIsSuitable(fact, width)) {
+    return false;
+  }
+
+  auto required_words = (width + 32 - 1) / 32;
+  if (static_cast<uint32_t>(fact.constant_word().size()) != required_words) {
+    return false;
+  }
+  facts_and_type_ids_.emplace_back(
+      std::pair<protobufs::FactConstantUniform, uint32_t>(
+          fact, final_element_type_id));
+  return true;
+}
+
+const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+ConstantUniformFacts::GetConstantUniformFactsAndTypes() const {
+  return facts_and_type_ids_;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fact_manager/constant_uniform_facts.h b/source/fuzz/fact_manager/constant_uniform_facts.h
new file mode 100644
index 0000000..41d253e
--- /dev/null
+++ b/source/fuzz/fact_manager/constant_uniform_facts.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_
+
+#include <vector>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about uniform constants.
+class ConstantUniformFacts {
+ public:
+  explicit ConstantUniformFacts(opt::IRContext* ir_context);
+
+  // See method in FactManager which delegates to this method.
+  bool MaybeAddFact(const protobufs::FactConstantUniform& fact);
+
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
+      uint32_t type_id) const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<protobufs::UniformBufferElementDescriptor>
+  GetUniformDescriptorsForConstant(uint32_t constant_id) const;
+
+  // See method in FactManager which delegates to this method.
+  uint32_t GetConstantFromUniformDescriptor(
+      const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
+      const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetTypesForWhichUniformValuesAreKnown() const;
+
+  // See method in FactManager which delegates to this method.
+  const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+  GetConstantUniformFactsAndTypes() const;
+
+ private:
+  // Returns true if and only if the words associated with
+  // |constant_instruction| exactly match the words for the constant associated
+  // with |constant_uniform_fact|.
+  static bool DataMatches(
+      const opt::Instruction& constant_instruction,
+      const protobufs::FactConstantUniform& constant_uniform_fact);
+
+  // Yields the constant words associated with |constant_uniform_fact|.
+  static std::vector<uint32_t> GetConstantWords(
+      const protobufs::FactConstantUniform& constant_uniform_fact);
+
+  // Yields the id of a constant of type |type_id| whose data matches the
+  // constant data in |constant_uniform_fact|, or 0 if no such constant is
+  // declared.
+  uint32_t GetConstantId(
+      const protobufs::FactConstantUniform& constant_uniform_fact,
+      uint32_t type_id) const;
+
+  // Checks that the width of a floating-point constant is supported, and that
+  // the constant is finite.
+  static bool FloatingPointValueIsSuitable(
+      const protobufs::FactConstantUniform& fact, uint32_t width);
+
+  std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>
+      facts_and_type_ids_;
+
+  opt::IRContext* ir_context_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_CONSTANT_UNIFORM_FACTS_H_
diff --git a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp
new file mode 100644
index 0000000..ad4cd0c
--- /dev/null
+++ b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.cpp
@@ -0,0 +1,938 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+size_t DataSynonymAndIdEquationFacts::OperationHash::operator()(
+    const Operation& operation) const {
+  std::u32string hash;
+  hash.push_back(operation.opcode);
+  for (auto operand : operation.operands) {
+    hash.push_back(static_cast<uint32_t>(DataDescriptorHash()(operand)));
+  }
+  return std::hash<std::u32string>()(hash);
+}
+
+bool DataSynonymAndIdEquationFacts::OperationEquals::operator()(
+    const Operation& first, const Operation& second) const {
+  // Equal operations require...
+  //
+  // Equal opcodes.
+  if (first.opcode != second.opcode) {
+    return false;
+  }
+  // Matching operand counts.
+  if (first.operands.size() != second.operands.size()) {
+    return false;
+  }
+  // Equal operands.
+  for (uint32_t i = 0; i < first.operands.size(); i++) {
+    if (!DataDescriptorEquals()(first.operands[i], second.operands[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+DataSynonymAndIdEquationFacts::DataSynonymAndIdEquationFacts(
+    opt::IRContext* ir_context)
+    : ir_context_(ir_context) {}
+
+bool DataSynonymAndIdEquationFacts::MaybeAddFact(
+    const protobufs::FactDataSynonym& fact,
+    const DeadBlockFacts& dead_block_facts,
+    const IrrelevantValueFacts& irrelevant_value_facts) {
+  if (irrelevant_value_facts.IdIsIrrelevant(fact.data1().object(),
+                                            dead_block_facts) ||
+      irrelevant_value_facts.IdIsIrrelevant(fact.data2().object(),
+                                            dead_block_facts)) {
+    // Irrelevant ids cannot be synonymous with other ids.
+    return false;
+  }
+
+  // Add the fact, including all facts relating sub-components of the data
+  // descriptors that are involved.
+  AddDataSynonymFactRecursive(fact.data1(), fact.data2());
+  return true;
+}
+
+bool DataSynonymAndIdEquationFacts::MaybeAddFact(
+    const protobufs::FactIdEquation& fact,
+    const DeadBlockFacts& dead_block_facts,
+    const IrrelevantValueFacts& irrelevant_value_facts) {
+  if (irrelevant_value_facts.IdIsIrrelevant(fact.lhs_id(), dead_block_facts)) {
+    // Irrelevant ids cannot participate in IdEquation facts.
+    return false;
+  }
+
+  for (auto id : fact.rhs_id()) {
+    if (irrelevant_value_facts.IdIsIrrelevant(id, dead_block_facts)) {
+      // Irrelevant ids cannot participate in IdEquation facts.
+      return false;
+    }
+  }
+
+  protobufs::DataDescriptor lhs_dd = MakeDataDescriptor(fact.lhs_id(), {});
+
+  // Register the LHS in the equivalence relation if needed.
+  RegisterDataDescriptor(lhs_dd);
+
+  // Get equivalence class representatives for all ids used on the RHS of the
+  // equation.
+  std::vector<const protobufs::DataDescriptor*> rhs_dds;
+  for (auto rhs_id : fact.rhs_id()) {
+    // Register a data descriptor based on this id in the equivalence relation
+    // if needed, and then record the equivalence class representative.
+    rhs_dds.push_back(RegisterDataDescriptor(MakeDataDescriptor(rhs_id, {})));
+  }
+
+  // Now add the fact.
+  AddEquationFactRecursive(lhs_dd, static_cast<SpvOp>(fact.opcode()), rhs_dds);
+  return true;
+}
+
+DataSynonymAndIdEquationFacts::OperationSet
+DataSynonymAndIdEquationFacts::GetEquations(
+    const protobufs::DataDescriptor* lhs) const {
+  auto existing = id_equations_.find(lhs);
+  if (existing == id_equations_.end()) {
+    return OperationSet();
+  }
+  return existing->second;
+}
+
+void DataSynonymAndIdEquationFacts::AddEquationFactRecursive(
+    const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
+    const std::vector<const protobufs::DataDescriptor*>& rhs_dds) {
+  assert(synonymous_.Exists(lhs_dd) &&
+         "The LHS must be known to the equivalence relation.");
+  for (auto rhs_dd : rhs_dds) {
+    // Keep release compilers happy.
+    (void)(rhs_dd);
+    assert(synonymous_.Exists(*rhs_dd) &&
+           "The RHS operands must be known to the equivalence relation.");
+  }
+
+  auto lhs_dd_representative = synonymous_.Find(&lhs_dd);
+
+  if (id_equations_.count(lhs_dd_representative) == 0) {
+    // We have not seen an equation with this LHS before, so associate the LHS
+    // with an initially empty set.
+    id_equations_.insert({lhs_dd_representative, OperationSet()});
+  }
+
+  {
+    auto existing_equations = id_equations_.find(lhs_dd_representative);
+    assert(existing_equations != id_equations_.end() &&
+           "A set of operations should be present, even if empty.");
+
+    Operation new_operation = {opcode, rhs_dds};
+    if (existing_equations->second.count(new_operation)) {
+      // This equation is known, so there is nothing further to be done.
+      return;
+    }
+    // Add the equation to the set of known equations.
+    existing_equations->second.insert(new_operation);
+  }
+
+  // Now try to work out corollaries implied by the new equation and existing
+  // facts.
+  switch (opcode) {
+    case SpvOpConvertSToF:
+    case SpvOpConvertUToF:
+      ComputeConversionDataSynonymFacts(*rhs_dds[0]);
+      break;
+    case SpvOpBitcast: {
+      assert(DataDescriptorsAreWellFormedAndComparable(lhs_dd, *rhs_dds[0]) &&
+             "Operands of OpBitcast equation fact must have compatible types");
+      if (!synonymous_.IsEquivalent(lhs_dd, *rhs_dds[0])) {
+        AddDataSynonymFactRecursive(lhs_dd, *rhs_dds[0]);
+      }
+    } break;
+    case SpvOpIAdd: {
+      // Equation form: "a = b + c"
+      for (const auto& equation : GetEquations(rhs_dds[0])) {
+        if (equation.opcode == SpvOpISub) {
+          // Equation form: "a = (d - e) + c"
+          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) {
+            // Equation form: "a = (d - c) + c"
+            // We can thus infer "a = d"
+            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]);
+          }
+          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
+            // Equation form: "a = (c - e) + c"
+            // We can thus infer "a = -e"
+            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
+                                     {equation.operands[1]});
+          }
+        }
+      }
+      for (const auto& equation : GetEquations(rhs_dds[1])) {
+        if (equation.opcode == SpvOpISub) {
+          // Equation form: "a = b + (d - e)"
+          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) {
+            // Equation form: "a = b + (d - b)"
+            // We can thus infer "a = d"
+            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]);
+          }
+        }
+      }
+      break;
+    }
+    case SpvOpISub: {
+      // Equation form: "a = b - c"
+      for (const auto& equation : GetEquations(rhs_dds[0])) {
+        if (equation.opcode == SpvOpIAdd) {
+          // Equation form: "a = (d + e) - c"
+          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
+            // Equation form: "a = (c + e) - c"
+            // We can thus infer "a = e"
+            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1]);
+          }
+          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[1])) {
+            // Equation form: "a = (d + c) - c"
+            // We can thus infer "a = d"
+            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]);
+          }
+        }
+
+        if (equation.opcode == SpvOpISub) {
+          // Equation form: "a = (d - e) - c"
+          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[1])) {
+            // Equation form: "a = (c - e) - c"
+            // We can thus infer "a = -e"
+            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
+                                     {equation.operands[1]});
+          }
+        }
+      }
+
+      for (const auto& equation : GetEquations(rhs_dds[1])) {
+        if (equation.opcode == SpvOpIAdd) {
+          // Equation form: "a = b - (d + e)"
+          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) {
+            // Equation form: "a = b - (b + e)"
+            // We can thus infer "a = -e"
+            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
+                                     {equation.operands[1]});
+          }
+          if (synonymous_.IsEquivalent(*equation.operands[1], *rhs_dds[0])) {
+            // Equation form: "a = b - (d + b)"
+            // We can thus infer "a = -d"
+            AddEquationFactRecursive(lhs_dd, SpvOpSNegate,
+                                     {equation.operands[0]});
+          }
+        }
+        if (equation.opcode == SpvOpISub) {
+          // Equation form: "a = b - (d - e)"
+          if (synonymous_.IsEquivalent(*equation.operands[0], *rhs_dds[0])) {
+            // Equation form: "a = b - (b - e)"
+            // We can thus infer "a = e"
+            AddDataSynonymFactRecursive(lhs_dd, *equation.operands[1]);
+          }
+        }
+      }
+      break;
+    }
+    case SpvOpLogicalNot:
+    case SpvOpSNegate: {
+      // Equation form: "a = !b" or "a = -b"
+      for (const auto& equation : GetEquations(rhs_dds[0])) {
+        if (equation.opcode == opcode) {
+          // Equation form: "a = !!b" or "a = -(-b)"
+          // We can thus infer "a = b"
+          AddDataSynonymFactRecursive(lhs_dd, *equation.operands[0]);
+        }
+      }
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+void DataSynonymAndIdEquationFacts::AddDataSynonymFactRecursive(
+    const protobufs::DataDescriptor& dd1,
+    const protobufs::DataDescriptor& dd2) {
+  assert((!ObjectStillExists(dd1) || !ObjectStillExists(dd2) ||
+          DataDescriptorsAreWellFormedAndComparable(dd1, dd2)) &&
+         "Mismatched data descriptors.");
+
+  // Record that the data descriptors provided in the fact are equivalent.
+  MakeEquivalent(dd1, dd2);
+  assert(synonymous_.Find(&dd1) == synonymous_.Find(&dd2) &&
+         "|dd1| and |dd2| must have a single representative");
+
+  // Compute various corollary facts.
+
+  // |dd1| and |dd2| belong to the same equivalence class so it doesn't matter
+  // which one we use here.
+  ComputeConversionDataSynonymFacts(dd1);
+
+  ComputeCompositeDataSynonymFacts(dd1, dd2);
+}
+
+void DataSynonymAndIdEquationFacts::ComputeConversionDataSynonymFacts(
+    const protobufs::DataDescriptor& dd) {
+  assert(synonymous_.Exists(dd) &&
+         "|dd| should've been registered in the equivalence relation");
+
+  if (!ObjectStillExists(dd)) {
+    // The object is gone from the module, so we cannot proceed.
+    return;
+  }
+
+  const auto* type =
+      ir_context_->get_type_mgr()->GetType(fuzzerutil::WalkCompositeTypeIndices(
+          ir_context_, fuzzerutil::GetTypeId(ir_context_, dd.object()),
+          dd.index()));
+  assert(type && "Data descriptor has invalid type");
+
+  if ((type->AsVector() && type->AsVector()->element_type()->AsInteger()) ||
+      type->AsInteger()) {
+    // If there exist equation facts of the form |%a = opcode %representative|
+    // and |%b = opcode %representative| where |opcode| is either OpConvertSToF
+    // or OpConvertUToF, then |a| and |b| are synonymous.
+    std::vector<const protobufs::DataDescriptor*> convert_s_to_f_lhs;
+    std::vector<const protobufs::DataDescriptor*> convert_u_to_f_lhs;
+
+    for (const auto& fact : id_equations_) {
+      auto equivalence_class = synonymous_.GetEquivalenceClass(*fact.first);
+      auto dd_it =
+          std::find_if(equivalence_class.begin(), equivalence_class.end(),
+                       [this](const protobufs::DataDescriptor* a) {
+                         return ObjectStillExists(*a);
+                       });
+      if (dd_it == equivalence_class.end()) {
+        // Skip |equivalence_class| if it has no valid ids.
+        continue;
+      }
+
+      for (const auto& equation : fact.second) {
+        if (synonymous_.IsEquivalent(*equation.operands[0], dd)) {
+          if (equation.opcode == SpvOpConvertSToF) {
+            convert_s_to_f_lhs.push_back(*dd_it);
+          } else if (equation.opcode == SpvOpConvertUToF) {
+            convert_u_to_f_lhs.push_back(*dd_it);
+          }
+        }
+      }
+    }
+
+    // We use pointers in the initializer list here since otherwise we would
+    // copy memory from these vectors.
+    for (const auto* synonyms : {&convert_s_to_f_lhs, &convert_u_to_f_lhs}) {
+      for (const auto* synonym_a : *synonyms) {
+        for (const auto* synonym_b : *synonyms) {
+          // DataDescriptorsAreWellFormedAndComparable will be called in the
+          // AddDataSynonymFactRecursive method.
+          if (!synonymous_.IsEquivalent(*synonym_a, *synonym_b)) {
+            // |synonym_a| and |synonym_b| have compatible types - they are
+            // synonymous.
+            AddDataSynonymFactRecursive(*synonym_a, *synonym_b);
+          }
+        }
+      }
+    }
+  }
+}
+
+void DataSynonymAndIdEquationFacts::ComputeCompositeDataSynonymFacts(
+    const protobufs::DataDescriptor& dd1,
+    const protobufs::DataDescriptor& dd2) {
+  // Check whether this is a synonym about composite objects. If it is,
+  // we can recursively add synonym facts about their associated sub-components.
+
+  // Get the type of the object referred to by the first data descriptor in the
+  // synonym fact.
+  uint32_t type_id = fuzzerutil::WalkCompositeTypeIndices(
+      ir_context_,
+      ir_context_->get_def_use_mgr()->GetDef(dd1.object())->type_id(),
+      dd1.index());
+  auto type = ir_context_->get_type_mgr()->GetType(type_id);
+  auto type_instruction = ir_context_->get_def_use_mgr()->GetDef(type_id);
+  assert(type != nullptr &&
+         "Invalid data synonym fact: one side has an unknown type.");
+
+  // Check whether the type is composite, recording the number of elements
+  // associated with the composite if so.
+  uint32_t num_composite_elements;
+  if (type->AsArray()) {
+    num_composite_elements =
+        fuzzerutil::GetArraySize(*type_instruction, ir_context_);
+  } else if (type->AsMatrix()) {
+    num_composite_elements = type->AsMatrix()->element_count();
+  } else if (type->AsStruct()) {
+    num_composite_elements =
+        fuzzerutil::GetNumberOfStructMembers(*type_instruction);
+  } else if (type->AsVector()) {
+    num_composite_elements = type->AsVector()->element_count();
+  } else {
+    // The type is not a composite, so return.
+    return;
+  }
+
+  // If the fact has the form:
+  //   obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
+  // then for each composite index i, we add a fact of the form:
+  //   obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
+  //
+  // However, to avoid adding a large number of synonym facts e.g. in the case
+  // of arrays, we bound the number of composite elements to which this is
+  // applied.  Nevertheless, we always add a synonym fact for the final
+  // components, as this may be an interesting edge case.
+
+  // The bound on the number of indices of the composite pair to note as being
+  // synonymous.
+  const uint32_t kCompositeElementBound = 10;
+
+  for (uint32_t i = 0; i < num_composite_elements;) {
+    std::vector<uint32_t> extended_indices1 =
+        fuzzerutil::RepeatedFieldToVector(dd1.index());
+    extended_indices1.push_back(i);
+    std::vector<uint32_t> extended_indices2 =
+        fuzzerutil::RepeatedFieldToVector(dd2.index());
+    extended_indices2.push_back(i);
+    AddDataSynonymFactRecursive(
+        MakeDataDescriptor(dd1.object(), extended_indices1),
+        MakeDataDescriptor(dd2.object(), extended_indices2));
+
+    if (i < kCompositeElementBound - 1 || i == num_composite_elements - 1) {
+      // We have not reached the bound yet, or have already skipped ahead to the
+      // last element, so increment the loop counter as standard.
+      i++;
+    } else {
+      // We have reached the bound, so skip ahead to the last element.
+      assert(i == kCompositeElementBound - 1);
+      i = num_composite_elements - 1;
+    }
+  }
+}
+
+void DataSynonymAndIdEquationFacts::ComputeClosureOfFacts(
+    uint32_t maximum_equivalence_class_size) {
+  // Suppose that obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n] are distinct
+  // data descriptors that describe objects of the same composite type, and that
+  // the composite type is comprised of k components.
+  //
+  // For example, if m is a mat4x4 and v a vec4, we might consider:
+  //   m[2]: describes the 2nd column of m, a vec4
+  //   v[]: describes all of v, a vec4
+  //
+  // Suppose that we know, for every 0 <= i < k, that the fact:
+  //   obj_1[a_1, ..., a_m, i] == obj_2[b_1, ..., b_n, i]
+  // holds - i.e. that the children of the two data descriptors are synonymous.
+  //
+  // Then we can conclude that:
+  //   obj_1[a_1, ..., a_m] == obj_2[b_1, ..., b_n]
+  // holds.
+  //
+  // For instance, if we have the facts:
+  //   m[2, 0] == v[0]
+  //   m[2, 1] == v[1]
+  //   m[2, 2] == v[2]
+  //   m[2, 3] == v[3]
+  // then we can conclude that:
+  //   m[2] == v.
+  //
+  // This method repeatedly searches the equivalence relation of data
+  // descriptors, deducing and adding such facts, until a pass over the
+  // relation leads to no further facts being deduced.
+
+  // The method relies on working with pairs of data descriptors, and in
+  // particular being able to hash and compare such pairs.
+
+  using DataDescriptorPair =
+      std::pair<protobufs::DataDescriptor, protobufs::DataDescriptor>;
+
+  struct DataDescriptorPairHash {
+    std::size_t operator()(const DataDescriptorPair& pair) const {
+      return DataDescriptorHash()(&pair.first) ^
+             DataDescriptorHash()(&pair.second);
+    }
+  };
+
+  struct DataDescriptorPairEquals {
+    bool operator()(const DataDescriptorPair& first,
+                    const DataDescriptorPair& second) const {
+      return (DataDescriptorEquals()(&first.first, &second.first) &&
+              DataDescriptorEquals()(&first.second, &second.second)) ||
+             (DataDescriptorEquals()(&first.first, &second.second) &&
+              DataDescriptorEquals()(&first.second, &second.first));
+    }
+  };
+
+  // This map records, for a given pair of composite data descriptors of the
+  // same type, all the indices at which the data descriptors are known to be
+  // synonymous.  A pair is a key to this map only if we have observed that
+  // the pair are synonymous at *some* index, but not at *all* indices.
+  // Once we find that a pair of data descriptors are equivalent at all indices
+  // we record the fact that they are synonymous and remove them from the map.
+  //
+  // Using the m and v example from above, initially the pair (m[2], v) would
+  // not be a key to the map.  If we find that m[2, 2] == v[2] holds, we would
+  // add an entry:
+  //   (m[2], v) -> [false, false, true, false]
+  // to record that they are synonymous at index 2.  If we then find that
+  // m[2, 0] == v[0] holds, we would update this entry to:
+  //   (m[2], v) -> [true, false, true, false]
+  // If we then find that m[2, 3] == v[3] holds, we would update this entry to:
+  //   (m[2], v) -> [true, false, true, true]
+  // Finally, if we then find that m[2, 1] == v[1] holds, which would make the
+  // boolean vector true at every index, we would add the fact:
+  //   m[2] == v
+  // to the equivalence relation and remove (m[2], v) from the map.
+  std::unordered_map<DataDescriptorPair, std::vector<bool>,
+                     DataDescriptorPairHash, DataDescriptorPairEquals>
+      candidate_composite_synonyms;
+
+  // We keep looking for new facts until we perform a complete pass over the
+  // equivalence relation without finding any new facts.
+  while (closure_computation_required_) {
+    // We have not found any new facts yet during this pass; we set this to
+    // 'true' if we do find a new fact.
+    closure_computation_required_ = false;
+
+    // Consider each class in the equivalence relation.
+    for (auto representative :
+         synonymous_.GetEquivalenceClassRepresentatives()) {
+      auto equivalence_class = synonymous_.GetEquivalenceClass(*representative);
+
+      if (equivalence_class.size() > maximum_equivalence_class_size) {
+        // This equivalence class is larger than the maximum size we are willing
+        // to consider, so we skip it.  This potentially leads to missed fact
+        // deductions, but avoids excessive runtime for closure computation.
+        continue;
+      }
+
+      // Consider every data descriptor in the equivalence class.
+      for (auto dd1_it = equivalence_class.begin();
+           dd1_it != equivalence_class.end(); ++dd1_it) {
+        // If this data descriptor has no indices then it does not have the form
+        // obj_1[a_1, ..., a_m, i], so move on.
+        auto dd1 = *dd1_it;
+        if (dd1->index_size() == 0) {
+          continue;
+        }
+
+        // Consider every other data descriptor later in the equivalence class
+        // (due to symmetry, there is no need to compare with previous data
+        // descriptors).
+        auto dd2_it = dd1_it;
+        for (++dd2_it; dd2_it != equivalence_class.end(); ++dd2_it) {
+          auto dd2 = *dd2_it;
+          // If this data descriptor has no indices then it does not have the
+          // form obj_2[b_1, ..., b_n, i], so move on.
+          if (dd2->index_size() == 0) {
+            continue;
+          }
+
+          // At this point we know that:
+          // - |dd1| has the form obj_1[a_1, ..., a_m, i]
+          // - |dd2| has the form obj_2[b_1, ..., b_n, j]
+          assert(dd1->index_size() > 0 && dd2->index_size() > 0 &&
+                 "Control should not reach here if either data descriptor has "
+                 "no indices.");
+
+          // We are only interested if i == j.
+          if (dd1->index(dd1->index_size() - 1) !=
+              dd2->index(dd2->index_size() - 1)) {
+            continue;
+          }
+
+          const uint32_t common_final_index = dd1->index(dd1->index_size() - 1);
+
+          // Make data descriptors |dd1_prefix| and |dd2_prefix| for
+          //   obj_1[a_1, ..., a_m]
+          // and
+          //   obj_2[b_1, ..., b_n]
+          // These are the two data descriptors we might be getting closer to
+          // deducing as being synonymous, due to knowing that they are
+          // synonymous when extended by a particular index.
+          protobufs::DataDescriptor dd1_prefix;
+          dd1_prefix.set_object(dd1->object());
+          for (uint32_t i = 0; i < static_cast<uint32_t>(dd1->index_size() - 1);
+               i++) {
+            dd1_prefix.add_index(dd1->index(i));
+          }
+          protobufs::DataDescriptor dd2_prefix;
+          dd2_prefix.set_object(dd2->object());
+          for (uint32_t i = 0; i < static_cast<uint32_t>(dd2->index_size() - 1);
+               i++) {
+            dd2_prefix.add_index(dd2->index(i));
+          }
+          assert(!DataDescriptorEquals()(&dd1_prefix, &dd2_prefix) &&
+                 "By construction these prefixes should be different.");
+
+          // If we already know that these prefixes are synonymous, move on.
+          if (synonymous_.Exists(dd1_prefix) &&
+              synonymous_.Exists(dd2_prefix) &&
+              synonymous_.IsEquivalent(dd1_prefix, dd2_prefix)) {
+            continue;
+          }
+          if (!ObjectStillExists(*dd1) || !ObjectStillExists(*dd2)) {
+            // The objects are not both available in the module, so we cannot
+            // investigate the types of the associated data descriptors; we need
+            // to move on.
+            continue;
+          }
+          // Get the type of obj_1
+          auto dd1_root_type_id =
+              fuzzerutil::GetTypeId(ir_context_, dd1->object());
+          // Use this type, together with a_1, ..., a_m, to get the type of
+          // obj_1[a_1, ..., a_m].
+          auto dd1_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
+              ir_context_, dd1_root_type_id, dd1_prefix.index());
+
+          // Similarly, get the type of obj_2 and use it to get the type of
+          // obj_2[b_1, ..., b_n].
+          auto dd2_root_type_id =
+              fuzzerutil::GetTypeId(ir_context_, dd2->object());
+          auto dd2_prefix_type = fuzzerutil::WalkCompositeTypeIndices(
+              ir_context_, dd2_root_type_id, dd2_prefix.index());
+
+          // If the types of dd1_prefix and dd2_prefix are not the same, they
+          // cannot be synonymous.
+          if (dd1_prefix_type != dd2_prefix_type) {
+            continue;
+          }
+
+          // At this point, we know we have synonymous data descriptors of the
+          // form:
+          //   obj_1[a_1, ..., a_m, i]
+          //   obj_2[b_1, ..., b_n, i]
+          // with the same last_index i, such that:
+          //   obj_1[a_1, ..., a_m]
+          // and
+          //   obj_2[b_1, ..., b_n]
+          // have the same type.
+
+          // Work out how many components there are in the (common) commposite
+          // type associated with obj_1[a_1, ..., a_m] and obj_2[b_1, ..., b_n].
+          // This depends on whether the composite type is array, matrix, struct
+          // or vector.
+          uint32_t num_components_in_composite;
+          auto composite_type =
+              ir_context_->get_type_mgr()->GetType(dd1_prefix_type);
+          auto composite_type_instruction =
+              ir_context_->get_def_use_mgr()->GetDef(dd1_prefix_type);
+          if (composite_type->AsArray()) {
+            num_components_in_composite = fuzzerutil::GetArraySize(
+                *composite_type_instruction, ir_context_);
+            if (num_components_in_composite == 0) {
+              // This indicates that the array has an unknown size, in which
+              // case we cannot be sure we have matched all of its elements with
+              // synonymous elements of another array.
+              continue;
+            }
+          } else if (composite_type->AsMatrix()) {
+            num_components_in_composite =
+                composite_type->AsMatrix()->element_count();
+          } else if (composite_type->AsStruct()) {
+            num_components_in_composite = fuzzerutil::GetNumberOfStructMembers(
+                *composite_type_instruction);
+          } else {
+            assert(composite_type->AsVector());
+            num_components_in_composite =
+                composite_type->AsVector()->element_count();
+          }
+
+          // We are one step closer to being able to say that |dd1_prefix| and
+          // |dd2_prefix| are synonymous.
+          DataDescriptorPair candidate_composite_synonym(dd1_prefix,
+                                                         dd2_prefix);
+
+          // We look up what we already know about this pair.
+          auto existing_entry =
+              candidate_composite_synonyms.find(candidate_composite_synonym);
+
+          if (existing_entry == candidate_composite_synonyms.end()) {
+            // If this is the first time we have seen the pair, we make a vector
+            // of size |num_components_in_composite| that is 'true' at the
+            // common final index associated with |dd1| and |dd2|, and 'false'
+            // everywhere else, and register this vector as being associated
+            // with the pair.
+            std::vector<bool> entry;
+            for (uint32_t i = 0; i < num_components_in_composite; i++) {
+              entry.push_back(i == common_final_index);
+            }
+            candidate_composite_synonyms[candidate_composite_synonym] = entry;
+            existing_entry =
+                candidate_composite_synonyms.find(candidate_composite_synonym);
+          } else {
+            // We have seen this pair of data descriptors before, and we now
+            // know that they are synonymous at one further index, so we
+            // update the entry to record that.
+            existing_entry->second[common_final_index] = true;
+          }
+          assert(existing_entry != candidate_composite_synonyms.end());
+
+          // Check whether |dd1_prefix| and |dd2_prefix| are now known to match
+          // at every sub-component.
+          bool all_components_match = true;
+          for (uint32_t i = 0; i < num_components_in_composite; i++) {
+            if (!existing_entry->second[i]) {
+              all_components_match = false;
+              break;
+            }
+          }
+          if (all_components_match) {
+            // The two prefixes match on all sub-components, so we know that
+            // they are synonymous.  We add this fact *non-recursively*, as we
+            // have deduced that |dd1_prefix| and |dd2_prefix| are synonymous
+            // by observing that all their sub-components are already
+            // synonymous.
+            assert(DataDescriptorsAreWellFormedAndComparable(dd1_prefix,
+                                                             dd2_prefix));
+            MakeEquivalent(dd1_prefix, dd2_prefix);
+            // Now that we know this pair of data descriptors are synonymous,
+            // there is no point recording how close they are to being
+            // synonymous.
+            candidate_composite_synonyms.erase(candidate_composite_synonym);
+          }
+        }
+      }
+    }
+  }
+}
+
+void DataSynonymAndIdEquationFacts::MakeEquivalent(
+    const protobufs::DataDescriptor& dd1,
+    const protobufs::DataDescriptor& dd2) {
+  // Register the data descriptors if they are not already known to the
+  // equivalence relation.
+  RegisterDataDescriptor(dd1);
+  RegisterDataDescriptor(dd2);
+
+  if (synonymous_.IsEquivalent(dd1, dd2)) {
+    // The data descriptors are already known to be equivalent, so there is
+    // nothing to do.
+    return;
+  }
+
+  // We must make the data descriptors equivalent, and also make sure any
+  // equation facts known about their representatives are merged.
+
+  // Record the original equivalence class representatives of the data
+  // descriptors.
+  auto dd1_original_representative = synonymous_.Find(&dd1);
+  auto dd2_original_representative = synonymous_.Find(&dd2);
+
+  // Make the data descriptors equivalent.
+  synonymous_.MakeEquivalent(dd1, dd2);
+  // As we have updated the equivalence relation, we might be able to deduce
+  // more facts by performing a closure computation, so we record that such a
+  // computation is required.
+  closure_computation_required_ = true;
+
+  // At this point, exactly one of |dd1_original_representative| and
+  // |dd2_original_representative| will be the representative of the combined
+  // equivalence class.  We work out which one of them is still the class
+  // representative and which one is no longer the class representative.
+
+  auto still_representative = synonymous_.Find(dd1_original_representative) ==
+                                      dd1_original_representative
+                                  ? dd1_original_representative
+                                  : dd2_original_representative;
+  auto no_longer_representative =
+      still_representative == dd1_original_representative
+          ? dd2_original_representative
+          : dd1_original_representative;
+
+  assert(no_longer_representative != still_representative &&
+         "The current and former representatives cannot be the same.");
+
+  // We now need to add all equations about |no_longer_representative| to the
+  // set of equations known about |still_representative|.
+
+  // Get the equations associated with |no_longer_representative|.
+  auto no_longer_representative_id_equations =
+      id_equations_.find(no_longer_representative);
+  if (no_longer_representative_id_equations != id_equations_.end()) {
+    // There are some equations to transfer.  There might not yet be any
+    // equations about |still_representative|; create an empty set of equations
+    // if this is the case.
+    if (!id_equations_.count(still_representative)) {
+      id_equations_.insert({still_representative, OperationSet()});
+    }
+    auto still_representative_id_equations =
+        id_equations_.find(still_representative);
+    assert(still_representative_id_equations != id_equations_.end() &&
+           "At this point there must be a set of equations.");
+    // Add all the equations known about |no_longer_representative| to the set
+    // of equations known about |still_representative|.
+    still_representative_id_equations->second.insert(
+        no_longer_representative_id_equations->second.begin(),
+        no_longer_representative_id_equations->second.end());
+  }
+  // Delete the no longer-relevant equations about |no_longer_representative|.
+  id_equations_.erase(no_longer_representative);
+}
+
+const protobufs::DataDescriptor*
+DataSynonymAndIdEquationFacts::RegisterDataDescriptor(
+    const protobufs::DataDescriptor& dd) {
+  return synonymous_.Exists(dd) ? synonymous_.Find(&dd)
+                                : synonymous_.Register(dd);
+}
+
+bool DataSynonymAndIdEquationFacts::DataDescriptorsAreWellFormedAndComparable(
+    const protobufs::DataDescriptor& dd1,
+    const protobufs::DataDescriptor& dd2) const {
+  if (!ObjectStillExists(dd1) || !ObjectStillExists(dd2)) {
+    // We trivially return true if one or other of the objects associated with
+    // the data descriptors is gone.
+    return true;
+  }
+
+  auto end_type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
+      ir_context_, fuzzerutil::GetTypeId(ir_context_, dd1.object()),
+      dd1.index());
+  auto end_type_id_2 = fuzzerutil::WalkCompositeTypeIndices(
+      ir_context_, fuzzerutil::GetTypeId(ir_context_, dd2.object()),
+      dd2.index());
+  // The end types of the data descriptors must exist.
+  if (end_type_id_1 == 0 || end_type_id_2 == 0) {
+    return false;
+  }
+  // Neither end type is allowed to be void.
+  if (ir_context_->get_def_use_mgr()->GetDef(end_type_id_1)->opcode() ==
+          SpvOpTypeVoid ||
+      ir_context_->get_def_use_mgr()->GetDef(end_type_id_2)->opcode() ==
+          SpvOpTypeVoid) {
+    return false;
+  }
+  // If the end types are the same, the data descriptors are comparable.
+  if (end_type_id_1 == end_type_id_2) {
+    return true;
+  }
+  // Otherwise they are only comparable if they are integer scalars or integer
+  // vectors that differ only in signedness.
+
+  // Get both types.
+  const auto* type_a = ir_context_->get_type_mgr()->GetType(end_type_id_1);
+  const auto* type_b = ir_context_->get_type_mgr()->GetType(end_type_id_2);
+  assert(type_a && type_b && "Data descriptors have invalid type(s)");
+
+  // If both types are numerical or vectors of numerical components, then they
+  // are compatible if they have the same number of components and the same bit
+  // count per component.
+
+  if (type_a->AsVector() && type_b->AsVector()) {
+    const auto* vector_a = type_a->AsVector();
+    const auto* vector_b = type_b->AsVector();
+
+    if (vector_a->element_count() != vector_b->element_count() ||
+        vector_a->element_type()->AsBool() ||
+        vector_b->element_type()->AsBool()) {
+      // The case where both vectors have boolean elements and the same number
+      // of components is handled by the direct equality check earlier.
+      // You can't have multiple identical boolean vector types.
+      return false;
+    }
+
+    type_a = vector_a->element_type();
+    type_b = vector_b->element_type();
+  }
+
+  auto get_bit_count_for_numeric_type =
+      [](const opt::analysis::Type& type) -> uint32_t {
+    if (const auto* integer = type.AsInteger()) {
+      return integer->width();
+    } else if (const auto* floating = type.AsFloat()) {
+      return floating->width();
+    } else {
+      assert(false && "|type| must be a numerical type");
+      return 0;
+    }
+  };
+
+  // Checks that both |type_a| and |type_b| are either numerical or vectors of
+  // numerical components and have the same number of bits.
+  return (type_a->AsInteger() || type_a->AsFloat()) &&
+         (type_b->AsInteger() || type_b->AsFloat()) &&
+         (get_bit_count_for_numeric_type(*type_a) ==
+          get_bit_count_for_numeric_type(*type_b));
+}
+
+std::vector<const protobufs::DataDescriptor*>
+DataSynonymAndIdEquationFacts::GetSynonymsForId(uint32_t id) const {
+  return GetSynonymsForDataDescriptor(MakeDataDescriptor(id, {}));
+}
+
+std::vector<const protobufs::DataDescriptor*>
+DataSynonymAndIdEquationFacts::GetSynonymsForDataDescriptor(
+    const protobufs::DataDescriptor& data_descriptor) const {
+  std::vector<const protobufs::DataDescriptor*> result;
+  if (synonymous_.Exists(data_descriptor)) {
+    for (auto dd : synonymous_.GetEquivalenceClass(data_descriptor)) {
+      // There may be data descriptors in the equivalence class whose base
+      // objects have been removed from the module.  We do not expose these
+      // data descriptors to clients of the fact manager.
+      if (ObjectStillExists(*dd)) {
+        result.push_back(dd);
+      }
+    }
+  }
+  return result;
+}
+
+std::vector<uint32_t>
+DataSynonymAndIdEquationFacts::GetIdsForWhichSynonymsAreKnown() const {
+  std::vector<uint32_t> result;
+  for (auto& data_descriptor : synonymous_.GetAllKnownValues()) {
+    // We skip any data descriptors whose base objects no longer exist in the
+    // module, and we restrict attention to data descriptors for plain ids,
+    // which have no indices.
+    if (ObjectStillExists(*data_descriptor) &&
+        data_descriptor->index().empty()) {
+      result.push_back(data_descriptor->object());
+    }
+  }
+  return result;
+}
+
+std::vector<const protobufs::DataDescriptor*>
+DataSynonymAndIdEquationFacts::GetAllKnownSynonyms() const {
+  std::vector<const protobufs::DataDescriptor*> result;
+  for (const auto* dd : synonymous_.GetAllKnownValues()) {
+    if (ObjectStillExists(*dd)) {
+      result.push_back(dd);
+    }
+  }
+  return result;
+}
+
+bool DataSynonymAndIdEquationFacts::IsSynonymous(
+    const protobufs::DataDescriptor& data_descriptor1,
+    const protobufs::DataDescriptor& data_descriptor2) const {
+  return synonymous_.Exists(data_descriptor1) &&
+         synonymous_.Exists(data_descriptor2) &&
+         synonymous_.IsEquivalent(data_descriptor1, data_descriptor2);
+}
+
+bool DataSynonymAndIdEquationFacts::ObjectStillExists(
+    const protobufs::DataDescriptor& dd) const {
+  return ir_context_->get_def_use_mgr()->GetDef(dd.object()) != nullptr;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h
new file mode 100644
index 0000000..6652f30
--- /dev/null
+++ b/source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h
@@ -0,0 +1,185 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_
+
+#include <unordered_set>
+#include <vector>
+
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/equivalence_relation.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// Forward reference to the DeadBlockFacts class.
+class DeadBlockFacts;
+// Forward reference to the IrrelevantValueFacts class.
+class IrrelevantValueFacts;
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about data synonyms and id equations.
+class DataSynonymAndIdEquationFacts {
+ public:
+  explicit DataSynonymAndIdEquationFacts(opt::IRContext* ir_context);
+
+  // See method in FactManager which delegates to this method. Returns true if
+  // neither |fact.data1()| nor |fact.data2()| contain an
+  // irrelevant id. Otherwise, returns false. |dead_block_facts| and
+  // |irrelevant_value_facts| are passed for consistency checks.
+  bool MaybeAddFact(const protobufs::FactDataSynonym& fact,
+                    const DeadBlockFacts& dead_block_facts,
+                    const IrrelevantValueFacts& irrelevant_value_facts);
+
+  // See method in FactManager which delegates to this method. Returns true if
+  // neither |fact.lhs_id()| nor any of |fact.rhs_id()| is irrelevant. Returns
+  // false otherwise. |dead_block_facts| and |irrelevant_value_facts| are passed
+  // for consistency checks.
+  bool MaybeAddFact(const protobufs::FactIdEquation& fact,
+                    const DeadBlockFacts& dead_block_facts,
+                    const IrrelevantValueFacts& irrelevant_value_facts);
+
+  // See method in FactManager which delegates to this method.
+  std::vector<const protobufs::DataDescriptor*> GetSynonymsForId(
+      uint32_t id) const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<const protobufs::DataDescriptor*> GetSynonymsForDataDescriptor(
+      const protobufs::DataDescriptor& data_descriptor) const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
+
+  // See method in FactManager which delegates to this method.
+  std::vector<const protobufs::DataDescriptor*> GetAllKnownSynonyms() const;
+
+  // See method in FactManager which delegates to this method.
+  bool IsSynonymous(const protobufs::DataDescriptor& data_descriptor1,
+                    const protobufs::DataDescriptor& data_descriptor2) const;
+
+  // See method in FactManager which delegates to this method.
+  void ComputeClosureOfFacts(uint32_t maximum_equivalence_class_size);
+
+ private:
+  // This helper struct represents the right hand side of an equation as an
+  // operator applied to a number of data descriptor operands.
+  struct Operation {
+    SpvOp opcode;
+    std::vector<const protobufs::DataDescriptor*> operands;
+  };
+
+  // Hashing for operations, to allow deterministic unordered sets.
+  struct OperationHash {
+    size_t operator()(const Operation& operation) const;
+  };
+
+  // Equality for operations, to allow deterministic unordered sets.
+  struct OperationEquals {
+    bool operator()(const Operation& first, const Operation& second) const;
+  };
+
+  using OperationSet =
+      std::unordered_set<Operation, OperationHash, OperationEquals>;
+
+  // Adds the synonym |dd1| = |dd2| to the set of managed facts, and recurses
+  // into sub-components of the data descriptors, if they are composites, to
+  // record that their components are pairwise-synonymous.
+  void AddDataSynonymFactRecursive(const protobufs::DataDescriptor& dd1,
+                                   const protobufs::DataDescriptor& dd2);
+
+  // Computes various corollary facts from the data descriptor |dd| if members
+  // of its equivalence class participate in equation facts with OpConvert*
+  // opcodes. The descriptor should be registered in the equivalence relation.
+  void ComputeConversionDataSynonymFacts(const protobufs::DataDescriptor& dd);
+
+  // Recurses into sub-components of the data descriptors, if they are
+  // composites, to record that their components are pairwise-synonymous.
+  void ComputeCompositeDataSynonymFacts(const protobufs::DataDescriptor& dd1,
+                                        const protobufs::DataDescriptor& dd2);
+
+  // Records the fact that |dd1| and |dd2| are equivalent, and merges the sets
+  // of equations that are known about them.
+  void MakeEquivalent(const protobufs::DataDescriptor& dd1,
+                      const protobufs::DataDescriptor& dd2);
+
+  // Registers a data descriptor in the equivalence relation if it hasn't been
+  // registered yet, and returns its representative.
+  const protobufs::DataDescriptor* RegisterDataDescriptor(
+      const protobufs::DataDescriptor& dd);
+
+  // Trivially returns true if either |dd1| or |dd2|'s objects are not present
+  // in the module.
+  //
+  // Otherwise, returns true if and only if |dd1| and |dd2| are valid data
+  // descriptors whose associated data have compatible types. Two types are
+  // compatible if:
+  // - they are the same
+  // - they both are numerical or vectors of numerical components with the same
+  //   number of components and the same bit count per component
+  bool DataDescriptorsAreWellFormedAndComparable(
+      const protobufs::DataDescriptor& dd1,
+      const protobufs::DataDescriptor& dd2) const;
+
+  OperationSet GetEquations(const protobufs::DataDescriptor* lhs) const;
+
+  // Requires that |lhs_dd| and every element of |rhs_dds| is present in the
+  // |synonymous_| equivalence relation, but is not necessarily its own
+  // representative.  Records the fact that the equation
+  // "|lhs_dd| |opcode| |rhs_dds_non_canonical|" holds, and adds any
+  // corollaries, in the form of data synonym or equation facts, that follow
+  // from this and other known facts.
+  void AddEquationFactRecursive(
+      const protobufs::DataDescriptor& lhs_dd, SpvOp opcode,
+      const std::vector<const protobufs::DataDescriptor*>& rhs_dds);
+
+  // Returns true if and only if |dd.object()| still exists in the module.
+  bool ObjectStillExists(const protobufs::DataDescriptor& dd) const;
+
+  // The data descriptors that are known to be synonymous with one another are
+  // captured by this equivalence relation.
+  EquivalenceRelation<protobufs::DataDescriptor, DataDescriptorHash,
+                      DataDescriptorEquals>
+      synonymous_;
+
+  // When a new synonym fact is added, it may be possible to deduce further
+  // synonym facts by computing a closure of all known facts.  However, this is
+  // an expensive operation, so it should be performed sparingly and only there
+  // is some chance of new facts being deduced.  This boolean tracks whether a
+  // closure computation is required - i.e., whether a new fact has been added
+  // since the last time such a computation was performed.
+  bool closure_computation_required_ = false;
+
+  // Represents a set of equations on data descriptors as a map indexed by
+  // left-hand-side, mapping a left-hand-side to a set of operations, each of
+  // which (together with the left-hand-side) defines an equation.
+  //
+  // All data descriptors occurring in equations are required to be present in
+  // the |synonymous_| equivalence relation, and to be their own representatives
+  // in that relation.
+  std::unordered_map<const protobufs::DataDescriptor*, OperationSet>
+      id_equations_;
+
+  // Pointer to the SPIR-V module we store facts about.
+  opt::IRContext* ir_context_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_DATA_SYNONYM_AND_ID_EQUATION_FACTS_H_
diff --git a/source/fuzz/fact_manager/dead_block_facts.cpp b/source/fuzz/fact_manager/dead_block_facts.cpp
new file mode 100644
index 0000000..f3e0ef8
--- /dev/null
+++ b/source/fuzz/fact_manager/dead_block_facts.cpp
@@ -0,0 +1,45 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/dead_block_facts.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+DeadBlockFacts::DeadBlockFacts(opt::IRContext* ir_context)
+    : ir_context_(ir_context) {}
+
+bool DeadBlockFacts::MaybeAddFact(const protobufs::FactBlockIsDead& fact) {
+  if (!fuzzerutil::MaybeFindBlock(ir_context_, fact.block_id())) {
+    return false;
+  }
+
+  dead_block_ids_.insert(fact.block_id());
+  return true;
+}
+
+bool DeadBlockFacts::BlockIsDead(uint32_t block_id) const {
+  return dead_block_ids_.count(block_id) != 0;
+}
+
+const std::unordered_set<uint32_t>& DeadBlockFacts::GetDeadBlocks() const {
+  return dead_block_ids_;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fact_manager/dead_block_facts.h b/source/fuzz/fact_manager/dead_block_facts.h
new file mode 100644
index 0000000..8ac5c3c
--- /dev/null
+++ b/source/fuzz/fact_manager/dead_block_facts.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_
+
+#include <unordered_set>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about data blocks.
+class DeadBlockFacts {
+ public:
+  explicit DeadBlockFacts(opt::IRContext* ir_context);
+
+  // Marks |fact.block_id()| as being dead. Returns true if |fact.block_id()|
+  // represents a result id of some OpLabel instruction in |ir_context_|.
+  // Returns false otherwise.
+  bool MaybeAddFact(const protobufs::FactBlockIsDead& fact);
+
+  // See method in FactManager which delegates to this method.
+  bool BlockIsDead(uint32_t block_id) const;
+
+  // Returns a set of all the block ids that have been declared dead.
+  const std::unordered_set<uint32_t>& GetDeadBlocks() const;
+
+ private:
+  std::unordered_set<uint32_t> dead_block_ids_;
+  opt::IRContext* ir_context_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_DEAD_BLOCK_FACTS_H_
diff --git a/source/fuzz/fact_manager/fact_manager.cpp b/source/fuzz/fact_manager/fact_manager.cpp
new file mode 100644
index 0000000..40c0865
--- /dev/null
+++ b/source/fuzz/fact_manager/fact_manager.cpp
@@ -0,0 +1,279 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "fact_manager.h"
+
+#include <sstream>
+#include <unordered_map>
+
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+std::string ToString(const protobufs::FactConstantUniform& fact) {
+  std::stringstream stream;
+  stream << "(" << fact.uniform_buffer_element_descriptor().descriptor_set()
+         << ", " << fact.uniform_buffer_element_descriptor().binding() << ")[";
+
+  bool first = true;
+  for (auto index : fact.uniform_buffer_element_descriptor().index()) {
+    if (first) {
+      first = false;
+    } else {
+      stream << ", ";
+    }
+    stream << index;
+  }
+
+  stream << "] == [";
+
+  first = true;
+  for (auto constant_word : fact.constant_word()) {
+    if (first) {
+      first = false;
+    } else {
+      stream << ", ";
+    }
+    stream << constant_word;
+  }
+
+  stream << "]";
+  return stream.str();
+}
+
+std::string ToString(const protobufs::FactDataSynonym& fact) {
+  std::stringstream stream;
+  stream << fact.data1() << " = " << fact.data2();
+  return stream.str();
+}
+
+std::string ToString(const protobufs::FactIdEquation& fact) {
+  std::stringstream stream;
+  stream << fact.lhs_id();
+  stream << " " << static_cast<SpvOp>(fact.opcode());
+  for (auto rhs_id : fact.rhs_id()) {
+    stream << " " << rhs_id;
+  }
+  return stream.str();
+}
+
+std::string ToString(const protobufs::Fact& fact) {
+  switch (fact.fact_case()) {
+    case protobufs::Fact::kConstantUniformFact:
+      return ToString(fact.constant_uniform_fact());
+    case protobufs::Fact::kDataSynonymFact:
+      return ToString(fact.data_synonym_fact());
+    case protobufs::Fact::kIdEquationFact:
+      return ToString(fact.id_equation_fact());
+    default:
+      assert(false && "Stringification not supported for this fact.");
+      return "";
+  }
+}
+
+}  // namespace
+
+FactManager::FactManager(opt::IRContext* ir_context)
+    : constant_uniform_facts_(ir_context),
+      data_synonym_and_id_equation_facts_(ir_context),
+      dead_block_facts_(ir_context),
+      livesafe_function_facts_(ir_context),
+      irrelevant_value_facts_(ir_context) {}
+
+void FactManager::AddInitialFacts(const MessageConsumer& message_consumer,
+                                  const protobufs::FactSequence& facts) {
+  for (auto& fact : facts.fact()) {
+    if (!MaybeAddFact(fact)) {
+      auto message = "Invalid fact " + ToString(fact) + " ignored.";
+      message_consumer(SPV_MSG_WARNING, nullptr, {}, message.c_str());
+    }
+  }
+}
+
+bool FactManager::MaybeAddFact(const fuzz::protobufs::Fact& fact) {
+  switch (fact.fact_case()) {
+    case protobufs::Fact::kBlockIsDeadFact:
+      return dead_block_facts_.MaybeAddFact(fact.block_is_dead_fact());
+    case protobufs::Fact::kConstantUniformFact:
+      return constant_uniform_facts_.MaybeAddFact(fact.constant_uniform_fact());
+    case protobufs::Fact::kDataSynonymFact:
+      return data_synonym_and_id_equation_facts_.MaybeAddFact(
+          fact.data_synonym_fact(), dead_block_facts_, irrelevant_value_facts_);
+    case protobufs::Fact::kFunctionIsLivesafeFact:
+      return livesafe_function_facts_.MaybeAddFact(
+          fact.function_is_livesafe_fact());
+    case protobufs::Fact::kIdEquationFact:
+      return data_synonym_and_id_equation_facts_.MaybeAddFact(
+          fact.id_equation_fact(), dead_block_facts_, irrelevant_value_facts_);
+    case protobufs::Fact::kIdIsIrrelevant:
+      return irrelevant_value_facts_.MaybeAddFact(
+          fact.id_is_irrelevant(), data_synonym_and_id_equation_facts_);
+    case protobufs::Fact::kPointeeValueIsIrrelevantFact:
+      return irrelevant_value_facts_.MaybeAddFact(
+          fact.pointee_value_is_irrelevant_fact(),
+          data_synonym_and_id_equation_facts_);
+    case protobufs::Fact::FACT_NOT_SET:
+      assert(false && "The fact must be set");
+      return false;
+  }
+
+  assert(false && "Unreachable");
+  return false;
+}
+
+void FactManager::AddFactDataSynonym(const protobufs::DataDescriptor& data1,
+                                     const protobufs::DataDescriptor& data2) {
+  protobufs::FactDataSynonym fact;
+  *fact.mutable_data1() = data1;
+  *fact.mutable_data2() = data2;
+  auto success = data_synonym_and_id_equation_facts_.MaybeAddFact(
+      fact, dead_block_facts_, irrelevant_value_facts_);
+  (void)success;  // Keep compilers happy in release mode.
+  assert(success && "Unable to create DataSynonym fact");
+}
+
+std::vector<uint32_t> FactManager::GetConstantsAvailableFromUniformsForType(
+    uint32_t type_id) const {
+  return constant_uniform_facts_.GetConstantsAvailableFromUniformsForType(
+      type_id);
+}
+
+std::vector<protobufs::UniformBufferElementDescriptor>
+FactManager::GetUniformDescriptorsForConstant(uint32_t constant_id) const {
+  return constant_uniform_facts_.GetUniformDescriptorsForConstant(constant_id);
+}
+
+uint32_t FactManager::GetConstantFromUniformDescriptor(
+    const protobufs::UniformBufferElementDescriptor& uniform_descriptor) const {
+  return constant_uniform_facts_.GetConstantFromUniformDescriptor(
+      uniform_descriptor);
+}
+
+std::vector<uint32_t> FactManager::GetTypesForWhichUniformValuesAreKnown()
+    const {
+  return constant_uniform_facts_.GetTypesForWhichUniformValuesAreKnown();
+}
+
+const std::vector<std::pair<protobufs::FactConstantUniform, uint32_t>>&
+FactManager::GetConstantUniformFactsAndTypes() const {
+  return constant_uniform_facts_.GetConstantUniformFactsAndTypes();
+}
+
+std::vector<uint32_t> FactManager::GetIdsForWhichSynonymsAreKnown() const {
+  return data_synonym_and_id_equation_facts_.GetIdsForWhichSynonymsAreKnown();
+}
+
+std::vector<const protobufs::DataDescriptor*> FactManager::GetAllSynonyms()
+    const {
+  return data_synonym_and_id_equation_facts_.GetAllKnownSynonyms();
+}
+
+std::vector<const protobufs::DataDescriptor*>
+FactManager::GetSynonymsForDataDescriptor(
+    const protobufs::DataDescriptor& data_descriptor) const {
+  return data_synonym_and_id_equation_facts_.GetSynonymsForDataDescriptor(
+      data_descriptor);
+}
+
+std::vector<const protobufs::DataDescriptor*> FactManager::GetSynonymsForId(
+    uint32_t id) const {
+  return data_synonym_and_id_equation_facts_.GetSynonymsForId(id);
+}
+
+bool FactManager::IsSynonymous(
+    const protobufs::DataDescriptor& data_descriptor1,
+    const protobufs::DataDescriptor& data_descriptor2) const {
+  return data_synonym_and_id_equation_facts_.IsSynonymous(data_descriptor1,
+                                                          data_descriptor2);
+}
+
+bool FactManager::BlockIsDead(uint32_t block_id) const {
+  return dead_block_facts_.BlockIsDead(block_id);
+}
+
+void FactManager::AddFactBlockIsDead(uint32_t block_id) {
+  protobufs::FactBlockIsDead fact;
+  fact.set_block_id(block_id);
+  auto success = dead_block_facts_.MaybeAddFact(fact);
+  (void)success;  // Keep compilers happy in release mode.
+  assert(success && "|block_id| is invalid");
+}
+
+bool FactManager::FunctionIsLivesafe(uint32_t function_id) const {
+  return livesafe_function_facts_.FunctionIsLivesafe(function_id);
+}
+
+void FactManager::AddFactFunctionIsLivesafe(uint32_t function_id) {
+  protobufs::FactFunctionIsLivesafe fact;
+  fact.set_function_id(function_id);
+  auto success = livesafe_function_facts_.MaybeAddFact(fact);
+  (void)success;  // Keep compilers happy in release mode.
+  assert(success && "|function_id| is invalid");
+}
+
+bool FactManager::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
+  return irrelevant_value_facts_.PointeeValueIsIrrelevant(pointer_id);
+}
+
+bool FactManager::IdIsIrrelevant(uint32_t result_id) const {
+  return irrelevant_value_facts_.IdIsIrrelevant(result_id, dead_block_facts_);
+}
+
+std::unordered_set<uint32_t> FactManager::GetIrrelevantIds() const {
+  return irrelevant_value_facts_.GetIrrelevantIds(dead_block_facts_);
+}
+
+void FactManager::AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id) {
+  protobufs::FactPointeeValueIsIrrelevant fact;
+  fact.set_pointer_id(pointer_id);
+  auto success = irrelevant_value_facts_.MaybeAddFact(
+      fact, data_synonym_and_id_equation_facts_);
+  (void)success;  // Keep compilers happy in release mode.
+  assert(success && "|pointer_id| is invalid");
+}
+
+void FactManager::AddFactIdIsIrrelevant(uint32_t result_id) {
+  protobufs::FactIdIsIrrelevant fact;
+  fact.set_result_id(result_id);
+  auto success = irrelevant_value_facts_.MaybeAddFact(
+      fact, data_synonym_and_id_equation_facts_);
+  (void)success;  // Keep compilers happy in release mode.
+  assert(success && "|result_id| is invalid");
+}
+
+void FactManager::AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
+                                    const std::vector<uint32_t>& rhs_id) {
+  protobufs::FactIdEquation fact;
+  fact.set_lhs_id(lhs_id);
+  fact.set_opcode(opcode);
+  for (auto an_rhs_id : rhs_id) {
+    fact.add_rhs_id(an_rhs_id);
+  }
+  auto success = data_synonym_and_id_equation_facts_.MaybeAddFact(
+      fact, dead_block_facts_, irrelevant_value_facts_);
+  (void)success;  // Keep compilers happy in release mode.
+  assert(success && "Can't create IdIsIrrelevant fact");
+}
+
+void FactManager::ComputeClosureOfFacts(
+    uint32_t maximum_equivalence_class_size) {
+  data_synonym_and_id_equation_facts_.ComputeClosureOfFacts(
+      maximum_equivalence_class_size);
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fact_manager.h b/source/fuzz/fact_manager/fact_manager.h
similarity index 71%
rename from source/fuzz/fact_manager.h
rename to source/fuzz/fact_manager/fact_manager.h
index f83e2ff..5cf5b18 100644
--- a/source/fuzz/fact_manager.h
+++ b/source/fuzz/fact_manager/fact_manager.h
@@ -12,15 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FACT_MANAGER_H_
-#define SOURCE_FUZZ_FACT_MANAGER_H_
+#ifndef SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_
+#define SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_
 
-#include <memory>
 #include <set>
 #include <utility>
 #include <vector>
 
 #include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fact_manager/constant_uniform_facts.h"
+#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h"
+#include "source/fuzz/fact_manager/dead_block_facts.h"
+#include "source/fuzz/fact_manager/irrelevant_value_facts.h"
+#include "source/fuzz/fact_manager/livesafe_function_facts.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/opt/constants.h"
 
@@ -38,47 +42,49 @@
 // the module.
 class FactManager {
  public:
-  FactManager();
-
-  ~FactManager();
+  explicit FactManager(opt::IRContext* ir_context);
 
   // Adds all the facts from |facts|, checking them for validity with respect to
-  // |context|.  Warnings about invalid facts are communicated via
+  // |ir_context_|. Warnings about invalid facts are communicated via
   // |message_consumer|; such facts are otherwise ignored.
-  void AddFacts(const MessageConsumer& message_consumer,
-                const protobufs::FactSequence& facts, opt::IRContext* context);
+  void AddInitialFacts(const MessageConsumer& message_consumer,
+                       const protobufs::FactSequence& facts);
 
-  // Checks the fact for validity with respect to |context|.  Returns false,
-  // with no side effects, if the fact is invalid.  Otherwise adds |fact| to the
+  // Checks the fact for validity with respect to |ir_context_|. Returns false,
+  // with no side effects, if the fact is invalid. Otherwise adds |fact| to the
   // fact manager.
-  bool AddFact(const protobufs::Fact& fact, opt::IRContext* context);
+  bool MaybeAddFact(const protobufs::Fact& fact);
 
-  // Record the fact that |data1| and |data2| are synonymous.
+  // Record the fact that |data1| and |data2| are synonymous. Neither |data1|
+  // nor |data2| may contain an irrelevant id.
   void AddFactDataSynonym(const protobufs::DataDescriptor& data1,
-                          const protobufs::DataDescriptor& data2,
-                          opt::IRContext* context);
+                          const protobufs::DataDescriptor& data2);
 
-  // Records the fact that |block_id| is dead.
+  // Records the fact that |block_id| is dead. |block_id| must be a result id
+  // of some OpLabel instruction in the |ir_context_|.
   void AddFactBlockIsDead(uint32_t block_id);
 
-  // Records the fact that |function_id| is livesafe.
+  // Records the fact that |function_id| is livesafe. |function_id| must be a
+  // result id of some non-entry-point function in the module.
   void AddFactFunctionIsLivesafe(uint32_t function_id);
 
   // Records the fact that the value of the pointee associated with |pointer_id|
   // is irrelevant: it does not affect the observable behaviour of the module.
+  // |pointer_id| must exist in the module and actually be a pointer.
   void AddFactValueOfPointeeIsIrrelevant(uint32_t pointer_id);
 
   // Records a fact that the |result_id| is irrelevant (i.e. it doesn't affect
-  // the semantics of the module)
+  // the semantics of the module).
+  // |result_id| must exist in the module and it may not be a pointer.
   void AddFactIdIsIrrelevant(uint32_t result_id);
 
   // Records the fact that |lhs_id| is defined by the equation:
   //
   //   |lhs_id| = |opcode| |rhs_id[0]| ... |rhs_id[N-1]|
   //
+  // Neither |lhs_id| nor any of |rhs_id| may be irrelevant.
   void AddFactIdEquation(uint32_t lhs_id, SpvOp opcode,
-                         const std::vector<uint32_t>& rhs_id,
-                         opt::IRContext* context);
+                         const std::vector<uint32_t>& rhs_id);
 
   // Inspects all known facts and adds corollary facts; e.g. if we know that
   // a.x == b.x and a.y == b.y, where a and b have vec2 type, we can record
@@ -92,8 +98,7 @@
   // The parameter |maximum_equivalence_class_size| specifies the size beyond
   // which equivalence classes should not be mined for new facts, to avoid
   // excessively-long closure computations.
-  void ComputeClosureOfFacts(opt::IRContext* ir_context,
-                             uint32_t maximum_equivalence_class_size);
+  void ComputeClosureOfFacts(uint32_t maximum_equivalence_class_size);
 
   // The fact manager is responsible for managing a few distinct categories of
   // facts. In principle there could be different fact managers for each kind
@@ -113,20 +118,18 @@
   // "constant == uniform element" fact is known.  If multiple identically-
   // valued constants are relevant, only one will appear in the sequence.
   std::vector<uint32_t> GetConstantsAvailableFromUniformsForType(
-      opt::IRContext* ir_context, uint32_t type_id) const;
+      uint32_t type_id) const;
 
   // Provides details of all uniform elements that are known to be equal to the
-  // constant associated with |constant_id| in |ir_context|.
-  const std::vector<protobufs::UniformBufferElementDescriptor>
-  GetUniformDescriptorsForConstant(opt::IRContext* ir_context,
-                                   uint32_t constant_id) const;
+  // constant associated with |constant_id| in |ir_context_|.
+  std::vector<protobufs::UniformBufferElementDescriptor>
+  GetUniformDescriptorsForConstant(uint32_t constant_id) const;
 
   // Returns the id of a constant whose value is known to match that of
   // |uniform_descriptor|, and whose type matches the type of the uniform
   // element.  If multiple such constant is exist, the one that is returned
   // is arbitrary.  Returns 0 if no such constant id exists.
   uint32_t GetConstantFromUniformDescriptor(
-      opt::IRContext* context,
       const protobufs::UniformBufferElementDescriptor& uniform_descriptor)
       const;
 
@@ -146,6 +149,10 @@
   // this piece of data" is known.
   std::vector<uint32_t> GetIdsForWhichSynonymsAreKnown() const;
 
+  // Returns a vector of all data descriptors that participate in DataSynonym
+  // facts. All descriptors are guaranteed to exist in the |ir_context_|.
+  std::vector<const protobufs::DataDescriptor*> GetAllSynonyms() const;
+
   // Returns the equivalence class of all known synonyms of |id|, or an empty
   // set if no synonyms are known.
   std::vector<const protobufs::DataDescriptor*> GetSynonymsForId(
@@ -191,42 +198,28 @@
   // |pointer_id| is irrelevant.
   bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
 
-  // Returns true iff there exists a fact that the |result_id| is irrelevant.
+  // Returns true if there exists a fact that the |result_id| is irrelevant or
+  // if |result_id| is declared in a block that has been declared dead.
   bool IdIsIrrelevant(uint32_t result_id) const;
 
+  // Returns a set of all the ids which have been declared irrelevant, or which
+  // have been declared inside a dead block.
+  std::unordered_set<uint32_t> GetIrrelevantIds() const;
+
   // End of irrelevant value facts
   //==============================
 
  private:
-  // For each distinct kind of fact to be managed, we use a separate opaque
-  // class type.
-
-  class ConstantUniformFacts;  // Opaque class for management of
-                               // constant uniform facts.
-  std::unique_ptr<ConstantUniformFacts>
-      uniform_constant_facts_;  // Unique pointer to internal data.
-
-  class DataSynonymAndIdEquationFacts;  // Opaque class for management of data
-                                        // synonym and id equation facts.
-  std::unique_ptr<DataSynonymAndIdEquationFacts>
-      data_synonym_and_id_equation_facts_;  // Unique pointer to internal data.
-
-  class DeadBlockFacts;  // Opaque class for management of dead block facts.
-  std::unique_ptr<DeadBlockFacts>
-      dead_block_facts_;  // Unique pointer to internal data.
-
-  class LivesafeFunctionFacts;  // Opaque class for management of livesafe
-                                // function facts.
-  std::unique_ptr<LivesafeFunctionFacts>
-      livesafe_function_facts_;  // Unique pointer to internal data.
-
-  class IrrelevantValueFacts;  // Opaque class for management of
-  // facts about various irrelevant values in the module.
-  std::unique_ptr<IrrelevantValueFacts>
-      irrelevant_value_facts_;  // Unique pointer to internal data.
+  // Keep these in alphabetical order.
+  fact_manager::ConstantUniformFacts constant_uniform_facts_;
+  fact_manager::DataSynonymAndIdEquationFacts
+      data_synonym_and_id_equation_facts_;
+  fact_manager::DeadBlockFacts dead_block_facts_;
+  fact_manager::LivesafeFunctionFacts livesafe_function_facts_;
+  fact_manager::IrrelevantValueFacts irrelevant_value_facts_;
 };
 
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FACT_MANAGER_H_
+#endif  // SOURCE_FUZZ_FACT_MANAGER_FACT_MANAGER_H_
diff --git a/source/fuzz/fact_manager/irrelevant_value_facts.cpp b/source/fuzz/fact_manager/irrelevant_value_facts.cpp
new file mode 100644
index 0000000..07836ad
--- /dev/null
+++ b/source/fuzz/fact_manager/irrelevant_value_facts.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/irrelevant_value_facts.h"
+
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fact_manager/data_synonym_and_id_equation_facts.h"
+#include "source/fuzz/fact_manager/dead_block_facts.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+IrrelevantValueFacts::IrrelevantValueFacts(opt::IRContext* ir_context)
+    : ir_context_(ir_context) {}
+
+bool IrrelevantValueFacts::MaybeAddFact(
+    const protobufs::FactPointeeValueIsIrrelevant& fact,
+    const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts) {
+  const auto* inst = ir_context_->get_def_use_mgr()->GetDef(fact.pointer_id());
+  if (!inst || !inst->type_id()) {
+    // The id must exist in the module and have type id.
+    return false;
+  }
+
+  if (!ir_context_->get_type_mgr()->GetType(inst->type_id())->AsPointer()) {
+    // The id must be a pointer.
+    return false;
+  }
+
+  if (!data_synonym_and_id_equation_facts.GetSynonymsForId(fact.pointer_id())
+           .empty()) {
+    // Irrelevant id cannot participate in DataSynonym facts.
+    return false;
+  }
+
+  pointers_to_irrelevant_pointees_ids_.insert(fact.pointer_id());
+  return true;
+}
+
+bool IrrelevantValueFacts::MaybeAddFact(
+    const protobufs::FactIdIsIrrelevant& fact,
+    const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts) {
+  const auto* inst = ir_context_->get_def_use_mgr()->GetDef(fact.result_id());
+  if (!inst || !inst->type_id()) {
+    // The id must exist in the module and have type id.
+    return false;
+  }
+
+  if (ir_context_->get_type_mgr()->GetType(inst->type_id())->AsPointer()) {
+    // The id may not be a pointer.
+    return false;
+  }
+
+  if (!data_synonym_and_id_equation_facts.GetSynonymsForId(fact.result_id())
+           .empty()) {
+    // Irrelevant id cannot participate in DataSynonym facts.
+    return false;
+  }
+
+  irrelevant_ids_.insert(fact.result_id());
+  return true;
+}
+
+bool IrrelevantValueFacts::PointeeValueIsIrrelevant(uint32_t pointer_id) const {
+  return pointers_to_irrelevant_pointees_ids_.count(pointer_id) != 0;
+}
+
+bool IrrelevantValueFacts::IdIsIrrelevant(
+    uint32_t result_id, const DeadBlockFacts& dead_block_facts) const {
+  // The id is irrelevant if it has been declared irrelevant.
+  if (irrelevant_ids_.count(result_id)) {
+    return true;
+  }
+
+  // The id must have a non-pointer type to be irrelevant.
+  auto def = ir_context_->get_def_use_mgr()->GetDef(result_id);
+  if (!def) {
+    return false;
+  }
+  auto type = ir_context_->get_type_mgr()->GetType(def->type_id());
+  if (!type || type->AsPointer()) {
+    return false;
+  }
+
+  // The id is irrelevant if it is in a dead block.
+  return ir_context_->get_instr_block(result_id) &&
+         dead_block_facts.BlockIsDead(
+             ir_context_->get_instr_block(result_id)->id());
+}
+
+std::unordered_set<uint32_t> IrrelevantValueFacts::GetIrrelevantIds(
+    const DeadBlockFacts& dead_block_facts) const {
+  // Get all the ids that have been declared irrelevant.
+  auto irrelevant_ids = irrelevant_ids_;
+
+  // Get all the non-pointer ids declared in dead blocks that have a type.
+  for (uint32_t block_id : dead_block_facts.GetDeadBlocks()) {
+    auto block = fuzzerutil::MaybeFindBlock(ir_context_, block_id);
+    // It is possible and allowed for the block not to exist, e.g. it could have
+    // been merged with another block.
+    if (!block) {
+      continue;
+    }
+    block->ForEachInst([this, &irrelevant_ids](opt::Instruction* inst) {
+      // The instruction must have a result id and a type, and it must not be a
+      // pointer.
+      if (inst->HasResultId() && inst->type_id() &&
+          !ir_context_->get_type_mgr()->GetType(inst->type_id())->AsPointer()) {
+        irrelevant_ids.emplace(inst->result_id());
+      }
+    });
+  }
+
+  return irrelevant_ids;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fact_manager/irrelevant_value_facts.h b/source/fuzz/fact_manager/irrelevant_value_facts.h
new file mode 100644
index 0000000..9faddc0
--- /dev/null
+++ b/source/fuzz/fact_manager/irrelevant_value_facts.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_
+
+#include <unordered_set>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// Forward reference to the DataSynonymAndIdEquationFacts class.
+class DataSynonymAndIdEquationFacts;
+// Forward reference to the DeadBlockFacts class.
+class DeadBlockFacts;
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about various irrelevant values in the module.
+class IrrelevantValueFacts {
+ public:
+  explicit IrrelevantValueFacts(opt::IRContext* ir_context);
+
+  // See method in FactManager which delegates to this method. Returns true if
+  // |fact.pointer_id()| is a result id of pointer type in the |ir_context_| and
+  // |fact.pointer_id()| does not participate in DataSynonym facts. Returns
+  // false otherwise. |data_synonym_and_id_equation_facts| and |context| are
+  // passed for consistency checks.
+  bool MaybeAddFact(
+      const protobufs::FactPointeeValueIsIrrelevant& fact,
+      const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts);
+
+  // See method in FactManager which delegates to this method. Returns true if
+  // |fact.result_id()| is a result id of non-pointer type in the |ir_context_|
+  // and |fact.result_id()| does not participate in DataSynonym facts. Returns
+  // false otherwise. |data_synonym_and_id_equation_facts| and |context| are
+  // passed for consistency checks.
+  bool MaybeAddFact(
+      const protobufs::FactIdIsIrrelevant& fact,
+      const DataSynonymAndIdEquationFacts& data_synonym_and_id_equation_facts);
+
+  // See method in FactManager which delegates to this method.
+  bool PointeeValueIsIrrelevant(uint32_t pointer_id) const;
+
+  // See method in FactManager which delegates to this method.
+  // |dead_block_facts| and |context| are passed to check whether |result_id| is
+  // declared inside a dead block, in which case it is irrelevant.
+  bool IdIsIrrelevant(uint32_t result_id,
+                      const DeadBlockFacts& dead_block_facts) const;
+
+  // See method in FactManager which delegates to this method.
+  // |dead_block_facts| and |context| are passed to also add all the ids
+  // declared in dead blocks to the set of irrelevant ids.
+  std::unordered_set<uint32_t> GetIrrelevantIds(
+      const DeadBlockFacts& dead_block_facts) const;
+
+ private:
+  std::unordered_set<uint32_t> pointers_to_irrelevant_pointees_ids_;
+  std::unordered_set<uint32_t> irrelevant_ids_;
+  opt::IRContext* ir_context_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_IRRELEVANT_VALUE_FACTS_H_
diff --git a/source/fuzz/fact_manager/livesafe_function_facts.cpp b/source/fuzz/fact_manager/livesafe_function_facts.cpp
new file mode 100644
index 0000000..553ac57
--- /dev/null
+++ b/source/fuzz/fact_manager/livesafe_function_facts.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/livesafe_function_facts.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+LivesafeFunctionFacts::LivesafeFunctionFacts(opt::IRContext* ir_context)
+    : ir_context_(ir_context) {}
+
+bool LivesafeFunctionFacts::MaybeAddFact(
+    const protobufs::FactFunctionIsLivesafe& fact) {
+  if (!fuzzerutil::FindFunction(ir_context_, fact.function_id())) {
+    return false;
+  }
+
+  if (fuzzerutil::FunctionIsEntryPoint(ir_context_, fact.function_id())) {
+    return false;
+  }
+
+  livesafe_function_ids_.insert(fact.function_id());
+  return true;
+}
+
+bool LivesafeFunctionFacts::FunctionIsLivesafe(uint32_t function_id) const {
+  return livesafe_function_ids_.count(function_id) != 0;
+}
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fact_manager/livesafe_function_facts.h b/source/fuzz/fact_manager/livesafe_function_facts.h
new file mode 100644
index 0000000..2156d64
--- /dev/null
+++ b/source/fuzz/fact_manager/livesafe_function_facts.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_
+#define SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_
+
+#include <unordered_set>
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace fact_manager {
+
+// The purpose of this class is to group the fields and data used to represent
+// facts about livesafe functions.
+class LivesafeFunctionFacts {
+ public:
+  explicit LivesafeFunctionFacts(opt::IRContext* ir_context);
+
+  // See method in FactManager which delegates to this method. Returns true if
+  // |fact.function_id()| is a result id of some non-entry-point function in
+  // |ir_context_|. Returns false otherwise.
+  bool MaybeAddFact(const protobufs::FactFunctionIsLivesafe& fact);
+
+  // See method in FactManager which delegates to this method.
+  bool FunctionIsLivesafe(uint32_t function_id) const;
+
+ private:
+  std::unordered_set<uint32_t> livesafe_function_ids_;
+  opt::IRContext* ir_context_;
+};
+
+}  // namespace fact_manager
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FACT_MANAGER_LIVESAFE_FUNCTION_FACTS_H_
diff --git a/source/fuzz/force_render_red.cpp b/source/fuzz/force_render_red.cpp
index 5bf2879..ed60bd0 100644
--- a/source/fuzz/force_render_red.cpp
+++ b/source/fuzz/force_render_red.cpp
@@ -14,7 +14,7 @@
 
 #include "source/fuzz/force_render_red.h"
 
-#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/fact_manager/fact_manager.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation_context.h"
@@ -26,9 +26,6 @@
 #include "source/util/make_unique.h"
 #include "tools/util/cli_consumer.h"
 
-#include <algorithm>
-#include <utility>
-
 namespace spvtools {
 namespace fuzz {
 
@@ -153,7 +150,7 @@
                           MakeInstructionDescriptor(greater_than_instruction,
                                                     SpvOpFOrdGreaterThan, 0),
                           in_operand_index),
-      fact_manager.GetUniformDescriptorsForConstant(ir_context, constant_id)[0],
+      fact_manager.GetUniformDescriptorsForConstant(constant_id)[0],
       ir_context->TakeNextId(), ir_context->TakeNextId());
 }
 
@@ -185,12 +182,11 @@
   assert(ir_context);
 
   // Set up a fact manager with any given initial facts.
-  FactManager fact_manager;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(ir_context.get()), validator_options);
   for (auto& fact : initial_facts.fact()) {
-    fact_manager.AddFact(fact, ir_context.get());
+    transformation_context.GetFactManager()->MaybeAddFact(fact);
   }
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
 
   auto entry_point_function =
       FindFragmentShaderEntryPoint(ir_context.get(), message_consumer);
@@ -216,6 +212,7 @@
     auto new_exit_block = MakeUnique<opt::BasicBlock>(std::move(label));
     new_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
         ir_context.get(), SpvOpReturn, 0, 0, opt::Instruction::OperandList()));
+    new_exit_block->SetParent(entry_point_function);
     entry_point_function->AddBasicBlock(std::move(new_exit_block));
   }
 
@@ -267,7 +264,8 @@
 
     auto float_type_id = ir_context->get_type_mgr()->GetId(float_type);
     auto types_for_which_uniforms_are_known =
-        fact_manager.GetTypesForWhichUniformValuesAreKnown();
+        transformation_context.GetFactManager()
+            ->GetTypesForWhichUniformValuesAreKnown();
 
     // Check whether we have any float uniforms.
     if (std::find(types_for_which_uniforms_are_known.begin(),
@@ -276,8 +274,8 @@
       // We have at least one float uniform; let's see whether we have at least
       // two.
       auto available_constants =
-          fact_manager.GetConstantsAvailableFromUniformsForType(
-              ir_context.get(), float_type_id);
+          transformation_context.GetFactManager()
+              ->GetConstantsAvailableFromUniformsForType(float_type_id);
       if (available_constants.size() > 1) {
         // Grab the float constants associated with the first two known float
         // uniforms.
@@ -323,13 +321,13 @@
               id_guaranteed_to_be_false, greater_than_operands));
 
           first_greater_then_operand_replacement =
-              MakeConstantUniformReplacement(ir_context.get(), fact_manager,
-                                             smaller_constant,
-                                             id_guaranteed_to_be_false, 0);
+              MakeConstantUniformReplacement(
+                  ir_context.get(), *transformation_context.GetFactManager(),
+                  smaller_constant, id_guaranteed_to_be_false, 0);
           second_greater_then_operand_replacement =
-              MakeConstantUniformReplacement(ir_context.get(), fact_manager,
-                                             larger_constant,
-                                             id_guaranteed_to_be_false, 1);
+              MakeConstantUniformReplacement(
+                  ir_context.get(), *transformation_context.GetFactManager(),
+                  larger_constant, id_guaranteed_to_be_false, 1);
         }
       }
     }
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index f549590..4361283 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -16,11 +16,15 @@
 
 #include <cassert>
 #include <memory>
+#include <numeric>
 #include <sstream>
 
-#include "source/fuzz/fact_manager.h"
+#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"
+#include "source/fuzz/fuzzer_pass_add_composite_extract.h"
+#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
 #include "source/fuzz/fuzzer_pass_add_composite_types.h"
 #include "source/fuzz/fuzzer_pass_add_copy_memory.h"
 #include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
@@ -32,8 +36,12 @@
 #include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h"
 #include "source/fuzz/fuzzer_pass_add_loads.h"
 #include "source/fuzz/fuzzer_pass_add_local_variables.h"
+#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
+#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
+#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_parameters.h"
+#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
 #include "source/fuzz/fuzzer_pass_add_stores.h"
 #include "source/fuzz/fuzzer_pass_add_synonyms.h"
 #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h"
@@ -46,24 +54,48 @@
 #include "source/fuzz/fuzzer_pass_construct_composites.h"
 #include "source/fuzz/fuzzer_pass_copy_objects.h"
 #include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h"
+#include "source/fuzz/fuzzer_pass_expand_vector_reductions.h"
+#include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h"
+#include "source/fuzz/fuzzer_pass_inline_functions.h"
+#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
 #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
 #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
+#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
 #include "source/fuzz/fuzzer_pass_merge_blocks.h"
+#include "source/fuzz/fuzzer_pass_merge_function_returns.h"
+#include "source/fuzz/fuzzer_pass_mutate_pointers.h"
 #include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
 #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_instructions.h"
 #include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
+#include "source/fuzz/fuzzer_pass_propagate_instructions_down.h"
+#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h"
 #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
+#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h"
+#include "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h"
+#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
+#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h"
+#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h"
 #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
+#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
+#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h"
+#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h"
 #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
 #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
 #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_toggle_access_chain_instruction.h"
+#include "source/fuzz/fuzzer_pass_wrap_regions_in_selections.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/pseudo_random_generator.h"
 #include "source/fuzz/transformation_context.h"
 #include "source/opt/build_module.h"
 #include "source/spirv_fuzzer_options.h"
@@ -75,114 +107,97 @@
 namespace {
 const uint32_t kIdBoundGap = 100;
 
-const uint32_t kTransformationLimit = 500;
-
-const uint32_t kChanceOfApplyingAnotherPass = 85;
-
-// A convenience method to add a fuzzer pass to |passes| with probability 0.5.
-// All fuzzer passes take |ir_context|, |transformation_context|,
-// |fuzzer_context| and |transformation_sequence_out| as parameters.  Extra
-// arguments can be provided via |extra_args|.
-template <typename T, typename... Args>
-void MaybeAddPass(
-    std::vector<std::unique_ptr<FuzzerPass>>* passes,
-    opt::IRContext* ir_context, TransformationContext* transformation_context,
-    FuzzerContext* fuzzer_context,
-    protobufs::TransformationSequence* transformation_sequence_out,
-    Args&&... extra_args) {
-  if (fuzzer_context->ChooseEven()) {
-    passes->push_back(MakeUnique<T>(ir_context, transformation_context,
-                                    fuzzer_context, transformation_sequence_out,
-                                    std::forward<Args>(extra_args)...));
-  }
-}
+const uint32_t kTransformationLimit = 2000;
 
 }  // namespace
 
-struct Fuzzer::Impl {
-  Impl(spv_target_env env, uint32_t random_seed, bool validate_after_each_pass,
-       spv_validator_options options)
-      : target_env(env),
-        seed(random_seed),
-        validate_after_each_fuzzer_pass(validate_after_each_pass),
-        validator_options(options) {}
-
-  bool ApplyPassAndCheckValidity(FuzzerPass* pass,
-                                 const opt::IRContext& ir_context,
-                                 const spvtools::SpirvTools& tools) const;
-
-  const spv_target_env target_env;       // Target environment.
-  MessageConsumer consumer;              // Message consumer.
-  const uint32_t seed;                   // Seed for random number generator.
-  bool validate_after_each_fuzzer_pass;  // Determines whether the validator
-                                         // should be invoked after every fuzzer
-                                         // pass.
-  spv_validator_options validator_options;  // Options to control validation.
-};
-
-Fuzzer::Fuzzer(spv_target_env env, uint32_t seed,
+Fuzzer::Fuzzer(spv_target_env target_env, MessageConsumer consumer,
+               const std::vector<uint32_t>& binary_in,
+               const protobufs::FactSequence& initial_facts,
+               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)
-    : impl_(MakeUnique<Impl>(env, seed, validate_after_each_fuzzer_pass,
-                             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)),
+      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_() {}
 
 Fuzzer::~Fuzzer() = default;
 
-void Fuzzer::SetMessageConsumer(MessageConsumer c) {
-  impl_->consumer = std::move(c);
-}
-
-bool Fuzzer::Impl::ApplyPassAndCheckValidity(
-    FuzzerPass* pass, const opt::IRContext& ir_context,
-    const spvtools::SpirvTools& tools) const {
-  pass->Apply();
-  if (validate_after_each_fuzzer_pass) {
-    std::vector<uint32_t> binary_to_validate;
-    ir_context.module()->ToBinary(&binary_to_validate, false);
-    if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
-                        validator_options)) {
-      consumer(SPV_MSG_INFO, nullptr, {},
-               "Binary became invalid during fuzzing (set a breakpoint to "
-               "inspect); stopping.");
-      return false;
-    }
+template <typename FuzzerPassT, typename... Args>
+void Fuzzer::MaybeAddRepeatedPass(uint32_t percentage_chance_of_adding_pass,
+                                  RepeatedPassInstances* pass_instances,
+                                  Args&&... extra_args) {
+  if (enable_all_passes_ ||
+      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)...));
   }
-  return true;
 }
 
-Fuzzer::FuzzerResultStatus Fuzzer::Run(
-    const std::vector<uint32_t>& binary_in,
-    const protobufs::FactSequence& initial_facts,
-    const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
-    std::vector<uint32_t>* binary_out,
-    protobufs::TransformationSequence* transformation_sequence_out) const {
+template <typename FuzzerPassT, typename... Args>
+void Fuzzer::MaybeAddFinalPass(std::vector<std::unique_ptr<FuzzerPass>>* passes,
+                               Args&&... extra_args) {
+  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)...));
+  }
+}
+
+bool Fuzzer::ApplyPassAndCheckValidity(FuzzerPass* pass) const {
+  pass->Apply();
+  return !validate_after_each_fuzzer_pass_ ||
+         fuzzerutil::IsValidAndWellFormed(ir_context_.get(), validator_options_,
+                                          consumer_);
+}
+
+Fuzzer::FuzzerResult Fuzzer::Run() {
   // Check compatibility between the library version being linked with and the
   // header files being used.
   GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-  spvtools::SpirvTools tools(impl_->target_env);
-  tools.SetMessageConsumer(impl_->consumer);
+  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()) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Failed to create SPIRV-Tools interface; stopping.");
-    return Fuzzer::FuzzerResultStatus::kFailedToCreateSpirvToolsInterface;
+    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(),
-                      impl_->validator_options)) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Initial binary is invalid; stopping.");
-    return Fuzzer::FuzzerResultStatus::kInitialBinaryInvalid;
+  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.
-  std::unique_ptr<opt::IRContext> ir_context = BuildModule(
-      impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
-  assert(ir_context);
-
-  // Make a PRNG from the seed passed to the fuzzer on creation.
-  PseudoRandomGenerator random_generator(impl_->seed);
+  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
@@ -191,172 +206,189 @@
   //
   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) consider the
   //  case where the maximum id bound is reached.
-  auto minimum_fresh_id = ir_context->module()->id_bound() + kIdBoundGap;
-  FuzzerContext fuzzer_context(&random_generator, minimum_fresh_id);
+  auto minimum_fresh_id = ir_context_->module()->id_bound() + kIdBoundGap;
+  fuzzer_context_ =
+      MakeUnique<FuzzerContext>(random_generator_.get(), minimum_fresh_id);
 
-  FactManager fact_manager;
-  fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
-  TransformationContext transformation_context(&fact_manager,
-                                               impl_->validator_options);
+  transformation_context_ = MakeUnique<TransformationContext>(
+      MakeUnique<FactManager>(ir_context_.get()), validator_options_);
+  transformation_context_->GetFactManager()->AddInitialFacts(consumer_,
+                                                             initial_facts_);
 
-  // Apply some semantics-preserving passes.
-  std::vector<std::unique_ptr<FuzzerPass>> passes;
-  while (passes.empty()) {
-    MaybeAddPass<FuzzerPassAddAccessChains>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddCompositeTypes>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddCopyMemory>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddDeadBlocks>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddDeadBreaks>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddDeadContinues>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddEquationInstructions>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddFunctionCalls>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddGlobalVariables>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddImageSampleUnusedComponents>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddLoads>(&passes, ir_context.get(),
-                                     &transformation_context, &fuzzer_context,
-                                     transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddLocalVariables>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddParameters>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddStores>(&passes, ir_context.get(),
-                                      &transformation_context, &fuzzer_context,
-                                      transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddSynonyms>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassAddVectorShuffleInstructions>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassApplyIdSynonyms>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassConstructComposites>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassCopyObjects>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassDonateModules>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out, donor_suppliers);
-    MaybeAddPass<FuzzerPassInvertComparisonOperators>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassMergeBlocks>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassObfuscateConstants>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassOutlineFunctions>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassPermuteBlocks>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassPushIdsThroughVariables>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassReplaceParameterWithGlobal>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassReplaceLinearAlgebraInstructions>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassReplaceParamsWithStruct>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassSplitBlocks>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
-    MaybeAddPass<FuzzerPassSwapBranchConditionalOperands>(
-        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
-        transformation_sequence_out);
+  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;
   }
 
-  bool is_first = true;
-  while (static_cast<uint32_t>(
-             transformation_sequence_out->transformation_size()) <
-             kTransformationLimit &&
-         (is_first ||
-          fuzzer_context.ChoosePercentage(kChanceOfApplyingAnotherPass))) {
-    is_first = false;
-    if (!impl_->ApplyPassAndCheckValidity(
-            passes[fuzzer_context.RandomIndex(passes)].get(), *ir_context,
-            tools)) {
-      return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
+  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;
-  MaybeAddPass<FuzzerPassAdjustBranchWeights>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassAdjustFunctionControls>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassAdjustLoopControls>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassAdjustMemoryOperandsMasks>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassAdjustSelectionControls>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassInterchangeZeroLikeConstants>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassPermutePhiOperands>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassSwapCommutableOperands>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
-  MaybeAddPass<FuzzerPassToggleAccessChainInstruction>(
-      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
-      transformation_sequence_out);
+  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 (!impl_->ApplyPassAndCheckValidity(pass.get(), *ir_context, tools)) {
-      return Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule;
+    if (!ApplyPassAndCheckValidity(pass.get())) {
+      return {Fuzzer::FuzzerResultStatus::kFuzzerPassLedToInvalidModule,
+              std::vector<uint32_t>(), protobufs::TransformationSequence()};
     }
   }
-
   // Encode the module as a binary.
-  ir_context->module()->ToBinary(binary_out, false);
+  std::vector<uint32_t> binary_out;
+  ir_context_->module()->ToBinary(&binary_out, false);
 
-  return Fuzzer::FuzzerResultStatus::kComplete;
+  return {Fuzzer::FuzzerResultStatus::kComplete, std::move(binary_out),
+          std::move(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_ <= kTransformationLimit &&
+      "The number of repeated passes applied must not exceed its upper limit.");
+  if (num_repeated_passes_applied_ == kTransformationLimit) {
+    // Stop because fuzzing has got stuck.
+    return false;
+  }
+  auto transformations_applied_so_far =
+      static_cast<uint32_t>(transformation_sequence_out_.transformation_size());
+  if (transformations_applied_so_far >= kTransformationLimit) {
+    // Stop because we have reached the transformation limit.
+    return false;
+  }
+  auto chance_of_continuing = static_cast<uint32_t>(
+      100.0 * (1.0 - (static_cast<double>(transformations_applied_so_far) /
+                      static_cast<double>(kTransformationLimit))));
+  if (!fuzzer_context_->ChoosePercentage(chance_of_continuing)) {
+    // We have probabilistically decided to stop.
+    return false;
+  }
+  // Continue fuzzing!
+  num_repeated_passes_applied_++;
+  return true;
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer.h b/source/fuzz/fuzzer.h
index 6c3ef71..774457f 100644
--- a/source/fuzz/fuzzer.h
+++ b/source/fuzz/fuzzer.h
@@ -16,10 +16,17 @@
 #define SOURCE_FUZZ_FUZZER_H_
 
 #include <memory>
+#include <utility>
 #include <vector>
 
+#include "source/fuzz/fuzzer_context.h"
+#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_recommender.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/random_generator.h"
+#include "source/opt/ir_context.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -37,11 +44,27 @@
     kInitialBinaryInvalid,
   };
 
-  // Constructs a fuzzer from the given target environment |env|.  |seed| is a
-  // seed for pseudo-random number generation.
-  // |validate_after_each_fuzzer_pass| controls whether the validator will be
-  // invoked after every fuzzer pass is applied.
-  Fuzzer(spv_target_env env, uint32_t seed,
+  struct FuzzerResult {
+    FuzzerResultStatus status;
+    std::vector<uint32_t> transformed_binary;
+    protobufs::TransformationSequence applied_transformations;
+  };
+
+  // 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,
+         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);
 
@@ -53,26 +76,109 @@
 
   ~Fuzzer();
 
-  // Sets the message consumer to the given |consumer|. The |consumer| will be
-  // invoked once for each message communicated from the library.
-  void SetMessageConsumer(MessageConsumer consumer);
-
-  // Transforms |binary_in| to |binary_out| by running a number of randomized
-  // fuzzer passes.  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|.
-  // The transformation sequence that was applied is returned via
-  // |transformation_sequence_out|.
-  FuzzerResultStatus Run(
-      const std::vector<uint32_t>& binary_in,
-      const protobufs::FactSequence& initial_facts,
-      const std::vector<fuzzerutil::ModuleSupplier>& donor_suppliers,
-      std::vector<uint32_t>* binary_out,
-      protobufs::TransformationSequence* transformation_sequence_out) const;
+  // 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();
 
  private:
-  struct Impl;                  // Opaque struct for holding internal data.
-  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+  // A convenience method to add a repeated fuzzer pass to |pass_instances| with
+  // probability |percentage_chance_of_adding_pass|%, or with probability 100%
+  // if |enable_all_passes_| is true.
+  //
+  // All fuzzer passes take members |ir_context_|, |transformation_context_|,
+  // |fuzzer_context_| and |transformation_sequence_out_| as parameters.  Extra
+  // arguments can be provided via |extra_args|.
+  template <typename FuzzerPassT, typename... Args>
+  void MaybeAddRepeatedPass(uint32_t percentage_chance_of_adding_pass,
+                            RepeatedPassInstances* pass_instances,
+                            Args&&... extra_args);
+
+  // The same as the above, with |percentage_chance_of_adding_pass| == 50%.
+  template <typename FuzzerPassT, typename... Args>
+  void MaybeAddRepeatedPass(RepeatedPassInstances* pass_instances,
+                            Args&&... extra_args) {
+    MaybeAddRepeatedPass<FuzzerPassT>(50, pass_instances,
+                                      std::forward<Args>(extra_args)...);
+  }
+
+  // A convenience method to add a final fuzzer pass to |passes| with
+  // probability 50%, or with probability 100% if |enable_all_passes_| is true.
+  //
+  // All fuzzer passes take members |ir_context_|, |transformation_context_|,
+  // |fuzzer_context_| and |transformation_sequence_out_| as parameters.  Extra
+  // arguments can be provided via |extra_args|.
+  template <typename FuzzerPassT, typename... Args>
+  void MaybeAddFinalPass(std::vector<std::unique_ptr<FuzzerPass>>* passes,
+                         Args&&... extra_args);
+
+  // Decides whether to apply more repeated passes. The probability decreases as
+  // the number of transformations that have been applied increases.
+  bool ShouldContinueFuzzing();
+
+  // Applies |pass|, which must be a pass constructed with |ir_context|.
+  // If |validate_after_each_fuzzer_pass_| is not set, true is always returned.
+  // Otherwise, true is returned if and only if |ir_context| passes validation,
+  // every block has its enclosing function as its parent, and every
+  // 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_;
+
+  // 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_;
+
+  // Determines whether the validator should be invoked after every fuzzer pass.
+  bool validate_after_each_fuzzer_pass_;
+
+  // Options to control validation.
+  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_;
+
+  // Intermediate representation for the module being fuzzed, which gets
+  // mutated as fuzzing proceeds.
+  std::unique_ptr<opt::IRContext> ir_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
+  // is initially empty and grows as fuzzer passes are applied.
+  protobufs::TransformationSequence transformation_sequence_out_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 3f1fe16..65904dc 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -23,10 +23,20 @@
 // Default <minimum, maximum> pairs of probabilities for applying various
 // transformations. All values are percentages. Keep them in alphabetical order.
 
+const std::pair<uint32_t, uint32_t>
+    kChanceOfAcceptingRepeatedPassRecommendation = {70, 100};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingAccessChain = {5, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherPassToPassLoop = {85,
+                                                                            95};
 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> kChanceOfAddingBitInstructionSynonym = {5,
+                                                                            20};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfAddingBothBranchesWhenReplacingOpSelect = {40, 60};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeExtract = {20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeInsert = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingCopyMemory = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBreak = {5, 80};
@@ -38,13 +48,17 @@
     {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingLoad = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingLocalVariable = {20, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingLoopPreheader = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
     5, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingOpPhiSynonym = {5, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingSynonyms = {20, 50};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfAddingTrueBranchWhenReplacingOpSelect = {40, 60};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorShuffle = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAdjustingBranchWeights = {20, 90};
@@ -62,30 +76,65 @@
     50, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfCreatingIntSynonymsUsingLoops = {
+    5, 10};
 const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = {
+    20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfExpandingVectorReduction = {20,
+                                                                         90};
+const std::pair<uint32_t, uint32_t> kChanceOfFlatteningConditionalBranch = {45,
+                                                                            95};
+const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToExtractComposite = {
+    30, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
+    30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
     {50, 95};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym = {50, 80};
+const std::pair<uint32_t, uint32_t> kChanceOfInliningFunction = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
     10, 90};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfInterchangingSignednessOfIntegerOperands = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfInvertingComparisonOperators = {
     20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
+const std::pair<uint32_t, uint32_t> kChanceOfMakingVectorOperationDynamic = {
+    20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfMergingBlocks = {20, 95};
+const std::pair<uint32_t, uint32_t> kChanceOfMergingFunctionReturns = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
+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> kChanceOfPermutingInstructions = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsDown = {20,
+                                                                            70};
+const std::pair<uint32_t, uint32_t> kChanceOfPropagatingInstructionsUp = {20,
+                                                                          70};
 const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 70};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfReplacingBranchFromDeadBlockWithExit = {10, 65};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
     {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
     {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfReplacingIrrelevantId = {35, 95};
 const std::pair<uint32_t, uint32_t>
     kChanceOfReplacingLinearAlgebraInstructions = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingLoadStoreWithCopyMemory =
     {20, 90};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfReplacingOpPhiIdFromDeadPredecessor = {20, 90};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfReplacingOpSelectWithConditionalBranch = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = {
     30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = {
@@ -95,6 +144,8 @@
     {10, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfTogglingAccessChainInstruction = {
     20, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfWrappingRegionInSelection = {70,
+                                                                          90};
 
 // Default limits for various quantities that are chosen during fuzzing.
 // Keep them in alphabetical order.
@@ -137,12 +188,24 @@
           kGetDefaultMaxNumberOfParametersReplacedWithStruct),
       go_deeper_in_constant_obfuscation_(
           kDefaultGoDeeperInConstantObfuscation) {
+  chance_of_accepting_repeated_pass_recommendation_ =
+      ChooseBetweenMinAndMax(kChanceOfAcceptingRepeatedPassRecommendation);
   chance_of_adding_access_chain_ =
       ChooseBetweenMinAndMax(kChanceOfAddingAccessChain);
+  chance_of_adding_another_pass_to_pass_loop_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingAnotherPassToPassLoop);
   chance_of_adding_another_struct_field_ =
       ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
   chance_of_adding_array_or_struct_type_ =
       ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
+  chance_of_adding_bit_instruction_synonym_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingBitInstructionSynonym);
+  chance_of_adding_both_branches_when_replacing_opselect_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingBothBranchesWhenReplacingOpSelect);
+  chance_of_adding_composite_extract_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingCompositeExtract);
+  chance_of_adding_composite_insert_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert);
   chance_of_adding_copy_memory_ =
       ChooseBetweenMinAndMax(kChanceOfAddingCopyMemory);
   chance_of_adding_dead_block_ =
@@ -156,6 +219,8 @@
   chance_of_adding_global_variable_ =
       ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable);
   chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad);
+  chance_of_adding_loop_preheader_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingLoopPreheader);
   chance_of_adding_image_sample_unused_components_ =
       ChooseBetweenMinAndMax(kChanceOfAddingImageSampleUnusedComponents);
   chance_of_adding_local_variable_ =
@@ -164,11 +229,15 @@
       ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
   chance_of_adding_no_contraction_decoration_ =
       ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
+  chance_of_adding_opphi_synonym_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingOpPhiSynonym);
   chance_of_adding_parameters =
       ChooseBetweenMinAndMax(kChanceOfAddingParameters);
   chance_of_adding_relaxed_decoration_ =
       ChooseBetweenMinAndMax(kChanceOfAddingRelaxedDecoration);
   chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore);
+  chance_of_adding_true_branch_when_replacing_opselect_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingTrueBranchWhenReplacingOpSelect);
   chance_of_adding_vector_shuffle_ =
       ChooseBetweenMinAndMax(kChanceOfAddingVectorShuffle);
   chance_of_adding_vector_type_ =
@@ -193,39 +262,79 @@
   chance_of_constructing_composite_ =
       ChooseBetweenMinAndMax(kChanceOfConstructingComposite);
   chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
+  chance_of_creating_int_synonyms_using_loops_ =
+      ChooseBetweenMinAndMax(kChanceOfCreatingIntSynonymsUsingLoops);
   chance_of_donating_additional_module_ =
       ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
+  chance_of_duplicating_region_with_selection_ =
+      ChooseBetweenMinAndMax(kChanceOfDuplicatingRegionWithSelection);
+  chance_of_expanding_vector_reduction_ =
+      ChooseBetweenMinAndMax(kChanceOfExpandingVectorReduction);
+  chance_of_flattening_conditional_branch_ =
+      ChooseBetweenMinAndMax(kChanceOfFlatteningConditionalBranch);
+  chance_of_going_deeper_to_extract_composite_ =
+      ChooseBetweenMinAndMax(kChanceOfGoingDeeperToExtractComposite);
+  chance_of_going_deeper_to_insert_in_composite_ =
+      ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
   chance_of_going_deeper_when_making_access_chain_ =
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
+  chance_of_having_two_blocks_in_loop_to_create_int_synonym_ =
+      ChooseBetweenMinAndMax(kChanceOfHavingTwoBlocksInLoopToCreateIntSynonym);
+  chance_of_inlining_function_ =
+      ChooseBetweenMinAndMax(kChanceOfInliningFunction);
+  chance_of_interchanging_signedness_of_integer_operands_ =
+      ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands);
   chance_of_interchanging_zero_like_constants_ =
       ChooseBetweenMinAndMax(kChanceOfInterchangingZeroLikeConstants);
   chance_of_inverting_comparison_operators_ =
       ChooseBetweenMinAndMax(kChanceOfInvertingComparisonOperators);
   chance_of_making_donor_livesafe_ =
       ChooseBetweenMinAndMax(kChanceOfMakingDonorLivesafe);
+  chance_of_making_vector_operation_dynamic_ =
+      ChooseBetweenMinAndMax(kChanceOfMakingVectorOperationDynamic);
   chance_of_merging_blocks_ = ChooseBetweenMinAndMax(kChanceOfMergingBlocks);
+  chance_of_merging_function_returns_ =
+      ChooseBetweenMinAndMax(kChanceOfMergingFunctionReturns);
   chance_of_moving_block_down_ =
       ChooseBetweenMinAndMax(kChanceOfMovingBlockDown);
+  chance_of_mutating_pointer_ =
+      ChooseBetweenMinAndMax(kChanceOfMutatingPointer);
   chance_of_obfuscating_constant_ =
       ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
   chance_of_outlining_function_ =
       ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
+  chance_of_permuting_instructions_ =
+      ChooseBetweenMinAndMax(kChanceOfPermutingInstructions);
   chance_of_permuting_parameters_ =
       ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
   chance_of_permuting_phi_operands_ =
       ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands);
+  chance_of_propagating_instructions_down_ =
+      ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsDown);
+  chance_of_propagating_instructions_up_ =
+      ChooseBetweenMinAndMax(kChanceOfPropagatingInstructionsUp);
   chance_of_pushing_id_through_variable_ =
       ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
+  chance_of_replacing_add_sub_mul_with_carrying_extended_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingAddSubMulWithCarryingExtended);
+  chance_of_replacing_branch_from_dead_block_with_exit_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingBranchFromDeadBlockWithExit);
   chance_of_replacing_copy_memory_with_load_store_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore);
   chance_of_replacing_copyobject_with_store_load_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingCopyObjectWithStoreLoad);
   chance_of_replacing_id_with_synonym_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
+  chance_of_replacing_irrelevant_id_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingIrrelevantId);
   chance_of_replacing_linear_algebra_instructions_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions);
   chance_of_replacing_load_store_with_copy_memory_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingLoadStoreWithCopyMemory);
+  chance_of_replacing_opphi_id_from_dead_predecessor_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingOpPhiIdFromDeadPredecessor);
+  chance_of_replacing_opselect_with_conditional_branch_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingOpSelectWithConditionalBranch);
   chance_of_replacing_parameters_with_globals_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals);
   chance_of_replacing_parameters_with_struct_ =
@@ -235,6 +344,8 @@
       ChooseBetweenMinAndMax(kChanceOfSwappingConditionalBranchOperands);
   chance_of_toggling_access_chain_instruction_ =
       ChooseBetweenMinAndMax(kChanceOfTogglingAccessChainInstruction);
+  chance_of_wrapping_region_in_selection_ =
+      ChooseBetweenMinAndMax(kChanceOfWrappingRegionInSelection);
 }
 
 FuzzerContext::~FuzzerContext() = default;
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 3376c1e..9193dfc 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -106,15 +106,33 @@
 
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
+  uint32_t GetChanceOfAcceptingRepeatedPassRecommendation() {
+    return chance_of_accepting_repeated_pass_recommendation_;
+  }
   uint32_t GetChanceOfAddingAccessChain() {
     return chance_of_adding_access_chain_;
   }
+  uint32_t GetChanceOfAddingAnotherPassToPassLoop() {
+    return chance_of_adding_another_pass_to_pass_loop_;
+  }
   uint32_t GetChanceOfAddingAnotherStructField() {
     return chance_of_adding_another_struct_field_;
   }
   uint32_t GetChanceOfAddingArrayOrStructType() {
     return chance_of_adding_array_or_struct_type_;
   }
+  uint32_t GetChanceOfAddingBitInstructionSynonym() {
+    return chance_of_adding_bit_instruction_synonym_;
+  }
+  uint32_t GetChanceOfAddingBothBranchesWhenReplacingOpSelect() {
+    return chance_of_adding_both_branches_when_replacing_opselect_;
+  }
+  uint32_t GetChanceOfAddingCompositeExtract() {
+    return chance_of_adding_composite_extract_;
+  }
+  uint32_t GetChanceOfAddingCompositeInsert() {
+    return chance_of_adding_composite_insert_;
+  }
   uint32_t GetChanceOfAddingCopyMemory() {
     return chance_of_adding_copy_memory_;
   }
@@ -136,18 +154,27 @@
   uint32_t GetChanceOfAddingLocalVariable() {
     return chance_of_adding_local_variable_;
   }
+  uint32_t GetChanceOfAddingLoopPreheader() {
+    return chance_of_adding_loop_preheader_;
+  }
   uint32_t GetChanceOfAddingMatrixType() {
     return chance_of_adding_matrix_type_;
   }
   uint32_t GetChanceOfAddingNoContractionDecoration() {
     return chance_of_adding_no_contraction_decoration_;
   }
+  uint32_t GetChanceOfAddingOpPhiSynonym() {
+    return chance_of_adding_opphi_synonym_;
+  }
   uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; }
   uint32_t GetChanceOfAddingRelaxedDecoration() {
     return chance_of_adding_relaxed_decoration_;
   }
   uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; }
   uint32_t GetChanceOfAddingSynonyms() { return chance_of_adding_synonyms_; }
+  uint32_t GetChanceOfAddingTrueBranchWhenReplacingOpSelect() {
+    return chance_of_adding_true_branch_when_replacing_opselect_;
+  }
   uint32_t GetChanceOfAddingVectorShuffle() {
     return chance_of_adding_vector_shuffle_;
   }
@@ -180,12 +207,39 @@
     return chance_of_constructing_composite_;
   }
   uint32_t GetChanceOfCopyingObject() { return chance_of_copying_object_; }
+  uint32_t GetChanceOfCreatingIntSynonymsUsingLoops() {
+    return chance_of_creating_int_synonyms_using_loops_;
+  }
   uint32_t GetChanceOfDonatingAdditionalModule() {
     return chance_of_donating_additional_module_;
   }
+  uint32_t GetChanceOfDuplicatingRegionWithSelection() {
+    return chance_of_duplicating_region_with_selection_;
+  }
+  uint32_t GetChanceOfExpandingVectorReduction() {
+    return chance_of_expanding_vector_reduction_;
+  }
+  uint32_t GetChanceOfFlatteningConditionalBranch() {
+    return chance_of_flattening_conditional_branch_;
+  }
+  uint32_t GetChanceOfGoingDeeperToExtractComposite() {
+    return chance_of_going_deeper_to_extract_composite_;
+  }
+  uint32_t GetChanceOfGoingDeeperToInsertInComposite() {
+    return chance_of_going_deeper_to_insert_in_composite_;
+  }
   uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
     return chance_of_going_deeper_when_making_access_chain_;
   }
+  uint32_t GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym() {
+    return chance_of_having_two_blocks_in_loop_to_create_int_synonym_;
+  }
+  uint32_t GetChanceOfInliningFunction() {
+    return chance_of_inlining_function_;
+  }
+  uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() {
+    return chance_of_interchanging_signedness_of_integer_operands_;
+  }
   uint32_t GetChanceOfInterchangingZeroLikeConstants() {
     return chance_of_interchanging_zero_like_constants_;
   }
@@ -195,23 +249,45 @@
   uint32_t ChanceOfMakingDonorLivesafe() {
     return chance_of_making_donor_livesafe_;
   }
+  uint32_t GetChanceOfMakingVectorOperationDynamic() {
+    return chance_of_making_vector_operation_dynamic_;
+  }
   uint32_t GetChanceOfMergingBlocks() { return chance_of_merging_blocks_; }
+  uint32_t GetChanceOfMergingFunctionReturns() {
+    return chance_of_merging_function_returns_;
+  }
   uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; }
+  uint32_t GetChanceOfMutatingPointer() { return chance_of_mutating_pointer_; }
   uint32_t GetChanceOfObfuscatingConstant() {
     return chance_of_obfuscating_constant_;
   }
   uint32_t GetChanceOfOutliningFunction() {
     return chance_of_outlining_function_;
   }
+  uint32_t GetChanceOfPermutingInstructions() {
+    return chance_of_permuting_instructions_;
+  }
   uint32_t GetChanceOfPermutingParameters() {
     return chance_of_permuting_parameters_;
   }
   uint32_t GetChanceOfPermutingPhiOperands() {
     return chance_of_permuting_phi_operands_;
   }
+  uint32_t GetChanceOfPropagatingInstructionsDown() {
+    return chance_of_propagating_instructions_down_;
+  }
+  uint32_t GetChanceOfPropagatingInstructionsUp() {
+    return chance_of_propagating_instructions_up_;
+  }
   uint32_t GetChanceOfPushingIdThroughVariable() {
     return chance_of_pushing_id_through_variable_;
   }
+  uint32_t GetChanceOfReplacingAddSubMulWithCarryingExtended() {
+    return chance_of_replacing_add_sub_mul_with_carrying_extended_;
+  }
+  uint32_t GetChanceOfReplacingBranchFromDeadBlockWithExit() {
+    return chance_of_replacing_branch_from_dead_block_with_exit_;
+  }
   uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() {
     return chance_of_replacing_copy_memory_with_load_store_;
   }
@@ -221,12 +297,21 @@
   uint32_t GetChanceOfReplacingIdWithSynonym() {
     return chance_of_replacing_id_with_synonym_;
   }
+  uint32_t GetChanceOfReplacingIrrelevantId() {
+    return chance_of_replacing_irrelevant_id_;
+  }
   uint32_t GetChanceOfReplacingLinearAlgebraInstructions() {
     return chance_of_replacing_linear_algebra_instructions_;
   }
   uint32_t GetChanceOfReplacingLoadStoreWithCopyMemory() {
     return chance_of_replacing_load_store_with_copy_memory_;
   }
+  uint32_t GetChanceOfReplacingOpPhiIdFromDeadPredecessor() {
+    return chance_of_replacing_opphi_id_from_dead_predecessor_;
+  }
+  uint32_t GetChanceOfReplacingOpselectWithConditionalBranch() {
+    return chance_of_replacing_opselect_with_conditional_branch_;
+  }
   uint32_t GetChanceOfReplacingParametersWithGlobals() {
     return chance_of_replacing_parameters_with_globals_;
   }
@@ -240,6 +325,9 @@
   uint32_t GetChanceOfTogglingAccessChainInstruction() {
     return chance_of_toggling_access_chain_instruction_;
   }
+  uint32_t GetChanceOfWrappingRegionInSelection() {
+    return chance_of_wrapping_region_in_selection_;
+  }
 
   // Other functions to control transformations. Keep them in alphabetical
   // order.
@@ -275,9 +363,19 @@
 
     return components;
   }
+  uint32_t GetRandomCompositeExtractIndex(uint32_t number_of_members) {
+    assert(number_of_members > 0 && "Composite object must have some members");
+    return ChooseBetweenMinAndMax({0, number_of_members - 1});
+  }
   uint32_t GetRandomIndexForAccessChain(uint32_t composite_size_bound) {
     return random_generator_->RandomUint32(composite_size_bound);
   }
+  uint32_t GetRandomIndexForCompositeInsert(uint32_t number_of_components) {
+    return random_generator_->RandomUint32(number_of_components);
+  }
+  int64_t GetRandomValueForStepConstantInLoop() {
+    return random_generator_->RandomUint64(UINT64_MAX);
+  }
   uint32_t GetRandomLoopControlPartialCount() {
     return random_generator_->RandomUint32(max_loop_control_partial_count_);
   }
@@ -287,6 +385,9 @@
   uint32_t GetRandomLoopLimit() {
     return random_generator_->RandomUint32(max_loop_limit_);
   }
+  uint32_t GetRandomNumberOfLoopIterations(uint32_t max_num_iterations) {
+    return ChooseBetweenMinAndMax({1, max_num_iterations});
+  }
   uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) {
     assert(num_of_params < GetMaximumNumberOfFunctionParameters());
     return ChooseBetweenMinAndMax(
@@ -321,9 +422,15 @@
 
   // Probabilities associated with applying various transformations.
   // Keep them in alphabetical order.
+  uint32_t chance_of_accepting_repeated_pass_recommendation_;
   uint32_t chance_of_adding_access_chain_;
+  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_bit_instruction_synonym_;
+  uint32_t chance_of_adding_both_branches_when_replacing_opselect_;
+  uint32_t chance_of_adding_composite_extract_;
+  uint32_t chance_of_adding_composite_insert_;
   uint32_t chance_of_adding_copy_memory_;
   uint32_t chance_of_adding_dead_block_;
   uint32_t chance_of_adding_dead_break_;
@@ -333,12 +440,15 @@
   uint32_t chance_of_adding_image_sample_unused_components_;
   uint32_t chance_of_adding_load_;
   uint32_t chance_of_adding_local_variable_;
+  uint32_t chance_of_adding_loop_preheader_;
   uint32_t chance_of_adding_matrix_type_;
   uint32_t chance_of_adding_no_contraction_decoration_;
+  uint32_t chance_of_adding_opphi_synonym_;
   uint32_t chance_of_adding_parameters;
   uint32_t chance_of_adding_relaxed_decoration_;
   uint32_t chance_of_adding_store_;
   uint32_t chance_of_adding_synonyms_;
+  uint32_t chance_of_adding_true_branch_when_replacing_opselect_;
   uint32_t chance_of_adding_vector_shuffle_;
   uint32_t chance_of_adding_vector_type_;
   uint32_t chance_of_adjusting_branch_weights_;
@@ -351,28 +461,49 @@
   uint32_t chance_of_choosing_workgroup_storage_class_;
   uint32_t chance_of_constructing_composite_;
   uint32_t chance_of_copying_object_;
+  uint32_t chance_of_creating_int_synonyms_using_loops_;
   uint32_t chance_of_donating_additional_module_;
+  uint32_t chance_of_duplicating_region_with_selection_;
+  uint32_t chance_of_expanding_vector_reduction_;
+  uint32_t chance_of_flattening_conditional_branch_;
+  uint32_t chance_of_going_deeper_to_extract_composite_;
+  uint32_t chance_of_going_deeper_to_insert_in_composite_;
   uint32_t chance_of_going_deeper_when_making_access_chain_;
+  uint32_t chance_of_having_two_blocks_in_loop_to_create_int_synonym_;
+  uint32_t chance_of_inlining_function_;
+  uint32_t chance_of_interchanging_signedness_of_integer_operands_;
   uint32_t chance_of_interchanging_zero_like_constants_;
   uint32_t chance_of_inverting_comparison_operators_;
   uint32_t chance_of_making_donor_livesafe_;
+  uint32_t chance_of_making_vector_operation_dynamic_;
   uint32_t chance_of_merging_blocks_;
+  uint32_t chance_of_merging_function_returns_;
   uint32_t chance_of_moving_block_down_;
+  uint32_t chance_of_mutating_pointer_;
   uint32_t chance_of_obfuscating_constant_;
   uint32_t chance_of_outlining_function_;
+  uint32_t chance_of_permuting_instructions_;
   uint32_t chance_of_permuting_parameters_;
   uint32_t chance_of_permuting_phi_operands_;
+  uint32_t chance_of_propagating_instructions_down_;
+  uint32_t chance_of_propagating_instructions_up_;
   uint32_t chance_of_pushing_id_through_variable_;
+  uint32_t chance_of_replacing_add_sub_mul_with_carrying_extended_;
+  uint32_t chance_of_replacing_branch_from_dead_block_with_exit_;
   uint32_t chance_of_replacing_copy_memory_with_load_store_;
   uint32_t chance_of_replacing_copyobject_with_store_load_;
   uint32_t chance_of_replacing_id_with_synonym_;
+  uint32_t chance_of_replacing_irrelevant_id_;
   uint32_t chance_of_replacing_linear_algebra_instructions_;
   uint32_t chance_of_replacing_load_store_with_copy_memory_;
+  uint32_t chance_of_replacing_opphi_id_from_dead_predecessor_;
+  uint32_t chance_of_replacing_opselect_with_conditional_branch_;
   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_conditional_branch_operands_;
   uint32_t chance_of_toggling_access_chain_instruction_;
+  uint32_t chance_of_wrapping_region_in_selection_;
 
   // 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 ebd88d0..486f18f 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -17,12 +17,16 @@
 #include <set>
 
 #include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/transformation_add_constant_boolean.h"
 #include "source/fuzz/transformation_add_constant_composite.h"
 #include "source/fuzz/transformation_add_constant_null.h"
 #include "source/fuzz/transformation_add_constant_scalar.h"
 #include "source/fuzz/transformation_add_global_undef.h"
+#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_local_variable.h"
+#include "source/fuzz/transformation_add_loop_preheader.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
 #include "source/fuzz/transformation_add_type_float.h"
 #include "source/fuzz/transformation_add_type_function.h"
@@ -31,6 +35,7 @@
 #include "source/fuzz/transformation_add_type_pointer.h"
 #include "source/fuzz/transformation_add_type_struct.h"
 #include "source/fuzz/transformation_add_type_vector.h"
+#include "source/fuzz/transformation_split_block.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -96,6 +101,72 @@
 }
 
 void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
+    opt::Function* function,
+    std::function<
+        void(opt::BasicBlock* block, opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor)>
+        action) {
+  // Consider only reachable blocks. We do this in a separate loop to avoid
+  // recomputing the dominator analysis every time |action| changes the
+  // module.
+  std::vector<opt::BasicBlock*> reachable_blocks;
+
+  const auto* dominator_analysis =
+      GetIRContext()->GetDominatorAnalysis(function);
+  for (auto& block : *function) {
+    if (dominator_analysis->IsReachable(&block)) {
+      reachable_blocks.push_back(&block);
+    }
+  }
+
+  for (auto* block : reachable_blocks) {
+    // We now consider every instruction in the block, randomly deciding
+    // whether to apply a transformation before it.
+
+    // In order for transformations to insert new instructions, they need to
+    // be able to identify the instruction to insert before.  We describe an
+    // instruction via its opcode, 'opc', a base instruction 'base' that has a
+    // result id, and the number of instructions with opcode 'opc' that we
+    // should skip when searching from 'base' for the desired instruction.
+    // (An instruction that has a result id is represented by its own opcode,
+    // itself as 'base', and a skip-count of 0.)
+    std::vector<std::tuple<uint32_t, SpvOp, uint32_t>> base_opcode_skip_triples;
+
+    // The initial base instruction is the block label.
+    uint32_t base = block->id();
+
+    // Counts the number of times we have seen each opcode since we reset the
+    // base instruction.
+    std::map<SpvOp, uint32_t> skip_count;
+
+    // Consider every instruction in the block.  The label is excluded: it is
+    // only necessary to consider it as a base in case the first instruction
+    // in the block does not have a result id.
+    for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) {
+      if (inst_it->HasResultId()) {
+        // In the case that the instruction has a result id, we use the
+        // instruction as its own base, and clear the skip counts we have
+        // collected.
+        base = inst_it->result_id();
+        skip_count.clear();
+      }
+      const SpvOp opcode = inst_it->opcode();
+
+      // Invoke the provided function, which might apply a transformation.
+      action(block, inst_it,
+             MakeInstructionDescriptor(
+                 base, opcode,
+                 skip_count.count(opcode) ? skip_count.at(opcode) : 0));
+
+      if (!inst_it->HasResultId()) {
+        skip_count[opcode] =
+            skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
+      }
+    }
+  }
+}
+
+void FuzzerPass::ForEachInstructionWithInstructionDescriptor(
     std::function<
         void(opt::Function* function, opt::BasicBlock* block,
              opt::BasicBlock::iterator inst_it,
@@ -103,52 +174,13 @@
         action) {
   // Consider every block in every function.
   for (auto& function : *GetIRContext()->module()) {
-    for (auto& block : function) {
-      // We now consider every instruction in the block, randomly deciding
-      // whether to apply a transformation before it.
-
-      // In order for transformations to insert new instructions, they need to
-      // be able to identify the instruction to insert before.  We describe an
-      // instruction via its opcode, 'opc', a base instruction 'base' that has a
-      // result id, and the number of instructions with opcode 'opc' that we
-      // should skip when searching from 'base' for the desired instruction.
-      // (An instruction that has a result id is represented by its own opcode,
-      // itself as 'base', and a skip-count of 0.)
-      std::vector<std::tuple<uint32_t, SpvOp, uint32_t>>
-          base_opcode_skip_triples;
-
-      // The initial base instruction is the block label.
-      uint32_t base = block.id();
-
-      // Counts the number of times we have seen each opcode since we reset the
-      // base instruction.
-      std::map<SpvOp, uint32_t> skip_count;
-
-      // Consider every instruction in the block.  The label is excluded: it is
-      // only necessary to consider it as a base in case the first instruction
-      // in the block does not have a result id.
-      for (auto inst_it = block.begin(); inst_it != block.end(); ++inst_it) {
-        if (inst_it->HasResultId()) {
-          // In the case that the instruction has a result id, we use the
-          // instruction as its own base, and clear the skip counts we have
-          // collected.
-          base = inst_it->result_id();
-          skip_count.clear();
-        }
-        const SpvOp opcode = inst_it->opcode();
-
-        // Invoke the provided function, which might apply a transformation.
-        action(&function, &block, inst_it,
-               MakeInstructionDescriptor(
-                   base, opcode,
-                   skip_count.count(opcode) ? skip_count.at(opcode) : 0));
-
-        if (!inst_it->HasResultId()) {
-          skip_count[opcode] =
-              skip_count.count(opcode) ? skip_count.at(opcode) + 1 : 1;
-        }
-      }
-    }
+    ForEachInstructionWithInstructionDescriptor(
+        &function,
+        [&action, &function](
+            opt::BasicBlock* block, opt::BasicBlock::iterator inst_it,
+            const protobufs::InstructionDescriptor& instruction_descriptor) {
+          action(&function, block, inst_it, instruction_descriptor);
+        });
   }
 }
 
@@ -292,8 +324,6 @@
 uint32_t FuzzerPass::FindOrCreateFloatConstant(
     const std::vector<uint32_t>& words, uint32_t width, bool is_irrelevant) {
   auto float_type_id = FindOrCreateFloatType(width);
-  opt::analysis::FloatConstant float_constant(
-      GetIRContext()->get_type_mgr()->GetType(float_type_id)->AsFloat(), words);
   if (auto constant_id = fuzzerutil::MaybeGetScalarConstant(
           GetIRContext(), *GetTransformationContext(), words, float_type_id,
           is_irrelevant)) {
@@ -423,17 +453,21 @@
         }
         break;
       case SpvOpTypeStruct: {
-        // A struct type is basic if all of its members are basic.
-        bool all_members_are_basic_types = true;
-        for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
-          if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
-            all_members_are_basic_types = false;
-            break;
+        // A struct type is basic if it does not have the Block/BufferBlock
+        // decoration, and if all of its members are basic.
+        if (!fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
+                                                         inst.result_id())) {
+          bool all_members_are_basic_types = true;
+          for (uint32_t i = 0; i < inst.NumInOperands(); i++) {
+            if (!basic_types.count(inst.GetSingleWordInOperand(i))) {
+              all_members_are_basic_types = false;
+              break;
+            }
           }
-        }
-        if (all_members_are_basic_types) {
-          basic_types.insert(inst.result_id());
-          basic_type_to_pointers.insert({inst.result_id(), {}});
+          if (all_members_are_basic_types) {
+            basic_types.insert(inst.result_id());
+            basic_type_to_pointers.insert({inst.result_id(), {}});
+          }
         }
         break;
       }
@@ -499,6 +533,10 @@
           scalar_or_composite_type_id, is_irrelevant);
     }
     case SpvOpTypeStruct: {
+      assert(!fuzzerutil::HasBlockOrBufferBlockDecoration(
+                 GetIRContext(), scalar_or_composite_type_id) &&
+             "We do not construct constants of struct types decorated with "
+             "Block or BufferBlock.");
       std::vector<uint32_t> field_zero_ids;
       for (uint32_t index = 0; index < type_instruction->NumInOperands();
            index++) {
@@ -514,5 +552,203 @@
   }
 }
 
+void FuzzerPass::MaybeAddUseToReplace(
+    opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
+    std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
+        uses_to_replace) {
+  // Only consider this use if it is in a block
+  if (!GetIRContext()->get_instr_block(use_inst)) {
+    return;
+  }
+
+  // Get the index of the operand restricted to input operands.
+  uint32_t in_operand_index =
+      fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
+  auto id_use_descriptor =
+      MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
+  uses_to_replace->emplace_back(
+      std::make_pair(id_use_descriptor, replacement_id));
+}
+
+opt::BasicBlock* FuzzerPass::GetOrCreateSimpleLoopPreheader(
+    uint32_t header_id) {
+  auto header_block = fuzzerutil::MaybeFindBlock(GetIRContext(), header_id);
+
+  assert(header_block && header_block->IsLoopHeader() &&
+         "|header_id| should be the label id of a loop header");
+
+  auto predecessors = GetIRContext()->cfg()->preds(header_id);
+
+  assert(predecessors.size() >= 2 &&
+         "The block |header_id| should be reachable.");
+
+  auto function = header_block->GetParent();
+
+  if (predecessors.size() == 2) {
+    // The header has a single out-of-loop predecessor, which could be a
+    // preheader.
+
+    opt::BasicBlock* maybe_preheader;
+
+    if (GetIRContext()->GetDominatorAnalysis(function)->Dominates(
+            header_id, predecessors[0])) {
+      // The first predecessor is the back-edge block, because the header
+      // dominates it, so the second one is out of the loop.
+      maybe_preheader = &*function->FindBlock(predecessors[1]);
+    } else {
+      // The first predecessor is out of the loop.
+      maybe_preheader = &*function->FindBlock(predecessors[0]);
+    }
+
+    // |maybe_preheader| is a preheader if it branches unconditionally to
+    // the header. We also require it not to be a loop header.
+    if (maybe_preheader->terminator()->opcode() == SpvOpBranch &&
+        !maybe_preheader->IsLoopHeader()) {
+      return maybe_preheader;
+    }
+  }
+
+  // We need to add a preheader.
+
+  // Get a fresh id for the preheader.
+  uint32_t preheader_id = GetFuzzerContext()->GetFreshId();
+
+  // Get a fresh id for each OpPhi instruction, if there is more than one
+  // out-of-loop predecessor.
+  std::vector<uint32_t> phi_ids;
+  if (predecessors.size() > 2) {
+    header_block->ForEachPhiInst(
+        [this, &phi_ids](opt::Instruction* /* unused */) {
+          phi_ids.push_back(GetFuzzerContext()->GetFreshId());
+        });
+  }
+
+  // Add the preheader.
+  ApplyTransformation(
+      TransformationAddLoopPreheader(header_id, preheader_id, phi_ids));
+
+  // Make the newly-created preheader the new entry block.
+  return &*function->FindBlock(preheader_id);
+}
+
+opt::BasicBlock* FuzzerPass::SplitBlockAfterOpPhiOrOpVariable(
+    uint32_t block_id) {
+  auto block = fuzzerutil::MaybeFindBlock(GetIRContext(), block_id);
+  assert(block && "|block_id| must be a block label");
+  assert(!block->IsLoopHeader() && "|block_id| cannot be a loop header");
+
+  // Find the first non-OpPhi and non-OpVariable instruction.
+  auto non_phi_or_var_inst = &*block->begin();
+  while (non_phi_or_var_inst->opcode() == SpvOpPhi ||
+         non_phi_or_var_inst->opcode() == SpvOpVariable) {
+    non_phi_or_var_inst = non_phi_or_var_inst->NextNode();
+  }
+
+  // Split the block.
+  uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationSplitBlock(
+      MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst),
+      new_block_id));
+
+  // We need to return the newly-created block.
+  return &*block->GetParent()->FindBlock(new_block_id);
+}
+
+uint32_t FuzzerPass::FindOrCreateLocalVariable(
+    uint32_t pointer_type_id, uint32_t function_id,
+    bool pointee_value_is_irrelevant) {
+  auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
+  // No unused variables in release mode.
+  (void)pointer_type;
+  assert(pointer_type && pointer_type->AsPointer() &&
+         pointer_type->AsPointer()->storage_class() ==
+             SpvStorageClassFunction &&
+         "The pointer_type_id must refer to a defined pointer type with "
+         "storage class Function");
+  auto function = fuzzerutil::FindFunction(GetIRContext(), function_id);
+  assert(function && "The function must be defined.");
+
+  // First we try to find a suitable existing variable.
+  // All of the local variable declarations are located in the first block.
+  for (auto& instruction : *function->begin()) {
+    if (instruction.opcode() != SpvOpVariable) {
+      continue;
+    }
+    // The existing OpVariable must have type |pointer_type_id|.
+    if (instruction.type_id() != pointer_type_id) {
+      continue;
+    }
+    // Check if the found variable is marked with PointeeValueIsIrrelevant
+    // according to |pointee_value_is_irrelevant|.
+    if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
+            instruction.result_id()) != pointee_value_is_irrelevant) {
+      continue;
+    }
+    return instruction.result_id();
+  }
+
+  // No such variable was found. Apply a transformation to get one.
+  uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      GetIRContext(), pointer_type_id);
+  uint32_t result_id = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddLocalVariable(
+      result_id, pointer_type_id, function_id,
+      FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
+      pointee_value_is_irrelevant));
+  return result_id;
+}
+
+uint32_t FuzzerPass::FindOrCreateGlobalVariable(
+    uint32_t pointer_type_id, bool pointee_value_is_irrelevant) {
+  auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
+  // No unused variables in release mode.
+  (void)pointer_type;
+  assert(
+      pointer_type && pointer_type->AsPointer() &&
+      (pointer_type->AsPointer()->storage_class() == SpvStorageClassPrivate ||
+       pointer_type->AsPointer()->storage_class() ==
+           SpvStorageClassWorkgroup) &&
+      "The pointer_type_id must refer to a defined pointer type with storage "
+      "class Private or Workgroup");
+
+  // First we try to find a suitable existing variable.
+  for (auto& instruction : GetIRContext()->module()->types_values()) {
+    if (instruction.opcode() != SpvOpVariable) {
+      continue;
+    }
+    // The existing OpVariable must have type |pointer_type_id|.
+    if (instruction.type_id() != pointer_type_id) {
+      continue;
+    }
+    // Check if the found variable is marked with PointeeValueIsIrrelevant
+    // according to |pointee_value_is_irrelevant|.
+    if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
+            instruction.result_id()) != pointee_value_is_irrelevant) {
+      continue;
+    }
+    return instruction.result_id();
+  }
+
+  // No such variable was found. Apply a transformation to get one.
+  uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      GetIRContext(), pointer_type_id);
+  auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
+      GetIRContext(), pointer_type_id);
+  uint32_t result_id = GetFuzzerContext()->GetFreshId();
+
+  // A variable with storage class Workgroup shouldn't have an initializer.
+  if (storage_class == SpvStorageClassWorkgroup) {
+    ApplyTransformation(TransformationAddGlobalVariable(
+        result_id, pointer_type_id, SpvStorageClassWorkgroup, 0,
+        pointee_value_is_irrelevant));
+  } else {
+    ApplyTransformation(TransformationAddGlobalVariable(
+        result_id, pointer_type_id, SpvStorageClassPrivate,
+        FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
+        pointee_value_is_irrelevant));
+  }
+  return result_id;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 4731b74..da3f239 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -70,9 +70,9 @@
       std::function<bool(opt::IRContext*, opt::Instruction*)>
           instruction_is_relevant) const;
 
-  // A helper method that iterates through each instruction in each block, at
-  // all times tracking an instruction descriptor that allows the latest
-  // instruction to be located even if it has no result id.
+  // A helper method that iterates through each instruction in each reachable
+  // block of |function|, at all times tracking an instruction descriptor that
+  // allows the latest instruction to be located even if it has no result id.
   //
   // The code to manipulate the instruction descriptor is a bit fiddly.  The
   // point of this method is to avoiding having to duplicate it in multiple
@@ -87,6 +87,17 @@
   // whether to try to apply some transformation, and then - if selected - to
   // attempt to apply it.
   void ForEachInstructionWithInstructionDescriptor(
+      opt::Function* function,
+      std::function<
+          void(opt::BasicBlock* block, opt::BasicBlock::iterator inst_it,
+               const protobufs::InstructionDescriptor& instruction_descriptor)>
+          action);
+
+  // Applies the above overload of ForEachInstructionWithInstructionDescriptor
+  // to every function in the module, so that |action| is applied to an
+  // |instruction_descriptor| for every instruction, |inst_it|, of every |block|
+  // in every |function|.
+  void ForEachInstructionWithInstructionDescriptor(
       std::function<
           void(opt::Function* function, opt::BasicBlock* block,
                opt::BasicBlock::iterator inst_it,
@@ -100,7 +111,12 @@
                                        *GetTransformationContext()) &&
            "Transformation should be applicable by construction.");
     transformation.Apply(GetIRContext(), GetTransformationContext());
-    *GetTransformations()->add_transformation() = transformation.ToMessage();
+    protobufs::Transformation transformation_message =
+        transformation.ToMessage();
+    assert(transformation_message.transformation_case() !=
+               protobufs::Transformation::TRANSFORMATION_NOT_SET &&
+           "Bad transformation.");
+    *GetTransformations()->add_transformation() = transformation_message;
   }
 
   // A generic helper for applying a transformation only if it is applicable.
@@ -111,7 +127,12 @@
     if (transformation.IsApplicable(GetIRContext(),
                                     *GetTransformationContext())) {
       transformation.Apply(GetIRContext(), GetTransformationContext());
-      *GetTransformations()->add_transformation() = transformation.ToMessage();
+      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;
@@ -273,6 +294,52 @@
   uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id,
                                     bool is_irrelevant);
 
+  // Adds a pair (id_use_descriptor, |replacement_id|) to the vector
+  // |uses_to_replace|, where id_use_descriptor is the id use descriptor
+  // representing the usage of an id in the |use_inst| instruction, at operand
+  // index |use_index|, only if the instruction is in a basic block.
+  // If the instruction is not in a basic block, it does nothing.
+  void MaybeAddUseToReplace(
+      opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
+      std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
+          uses_to_replace);
+
+  // Returns the preheader of the loop with header |header_id|, which satisfies
+  // all of the following conditions:
+  // - It is the only out-of-loop predecessor of the header
+  // - It unconditionally branches to the header
+  // - It is not a loop header itself
+  // If such preheader does not exist, a new one is added and returned.
+  // Requires |header_id| to be the label id of a loop header block that is
+  // reachable in the CFG (and thus has at least 2 predecessors).
+  opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id);
+
+  // Returns the second block in the pair obtained by splitting |block_id| just
+  // after the last OpPhi or OpVariable instruction in it. Assumes that the
+  // block is not a loop header.
+  opt::BasicBlock* SplitBlockAfterOpPhiOrOpVariable(uint32_t block_id);
+
+  // Returns the id of an available local variable (storage class Function) with
+  // the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. If there is no such variable, it creates one
+  // in the |function| adding a zero initializer constant that is irrelevant.
+  // The new variable has the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. The function returns the id of the created
+  // variable.
+  uint32_t FindOrCreateLocalVariable(uint32_t pointer_type_id,
+                                     uint32_t function_id,
+                                     bool pointee_value_is_irrelevant);
+
+  // Returns the id of an available global variable (storage class Private or
+  // Workgroup) with the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. If there is no such variable, it creates
+  // one, adding a zero initializer constant that is irrelevant. The new
+  // variable has the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. The function returns the id of the created
+  // variable.
+  uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id,
+                                      bool pointee_value_is_irrelevant);
+
  private:
   opt::IRContext* ir_context_;
   TransformationContext* transformation_context_;
diff --git a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp
new file mode 100644
index 0000000..bd2dd3d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.cpp
@@ -0,0 +1,85 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_add_bit_instruction_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddBitInstructionSynonyms::FuzzerPassAddBitInstructionSynonyms(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddBitInstructionSynonyms::~FuzzerPassAddBitInstructionSynonyms() =
+    default;
+
+void FuzzerPassAddBitInstructionSynonyms::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        // Randomly decides whether the transformation will be applied.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingBitInstructionSynonym())) {
+          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()) {
+          continue;
+        }
+
+        // Make sure all bit indexes are defined as 32-bit unsigned integers.
+        uint32_t width = GetIRContext()
+                             ->get_type_mgr()
+                             ->GetType(instruction.type_id())
+                             ->AsInteger()
+                             ->width();
+        for (uint32_t i = 0; i < width; i++) {
+          FindOrCreateIntegerConstant({i}, 32, false, false);
+        }
+
+        // Applies the add bit instruction synonym transformation.
+        ApplyTransformation(TransformationAddBitInstructionSynonym(
+            instruction.result_id(),
+            GetFuzzerContext()->GetFreshIds(
+                TransformationAddBitInstructionSynonym::GetRequiredFreshIdCount(
+                    GetIRContext(), &instruction))));
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h
new file mode 100644
index 0000000..0194425
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_BIT_INSTRUCTION_SYNONYMS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_BIT_INSTRUCTION_SYNONYMS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This fuzzer pass adds synonyms for bit instructions. It iterates over the
+// module instructions, checks if they are bit instructions and randomly applies
+// the transformation.
+class FuzzerPassAddBitInstructionSynonyms : public FuzzerPass {
+ public:
+  FuzzerPassAddBitInstructionSynonyms(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddBitInstructionSynonyms();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_BIT_INSTRUCTION_SYNONYMS_H_
diff --git a/source/fuzz/fuzzer_pass_add_composite_extract.cpp b/source/fuzz/fuzzer_pass_add_composite_extract.cpp
new file mode 100644
index 0000000..3b40d5f
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_extract.cpp
@@ -0,0 +1,162 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_composite_extract.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_composite_extract.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddCompositeExtract::FuzzerPassAddCompositeExtract(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddCompositeExtract::~FuzzerPassAddCompositeExtract() = default;
+
+void FuzzerPassAddCompositeExtract::Apply() {
+  std::vector<const protobufs::DataDescriptor*> composite_synonyms;
+  for (const auto* dd :
+       GetTransformationContext()->GetFactManager()->GetAllSynonyms()) {
+    // |dd| must describe a component of a composite.
+    if (!dd->index().empty()) {
+      composite_synonyms.push_back(dd);
+    }
+  }
+
+  // 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;
+
+  ForEachInstructionWithInstructionDescriptor(
+      [this, &composite_synonyms, &transformations](
+          opt::Function* function, opt::BasicBlock* block,
+          opt::BasicBlock::iterator inst_it,
+          const protobufs::InstructionDescriptor& instruction_descriptor) {
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
+                                                          inst_it)) {
+          return;
+        }
+
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingCompositeExtract())) {
+          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(
+                  GetIRContext(), &*inst_it, dd->object())) {
+            available_synonyms.push_back(dd);
+          }
+        }
+
+        if (available_synonyms.empty() && available_composites.empty()) {
+          return;
+        }
+
+        uint32_t composite_id = 0;
+        std::vector<uint32_t> indices;
+
+        if (available_synonyms.empty() || (!available_composites.empty() &&
+                                           GetFuzzerContext()->ChooseEven())) {
+          const auto* inst =
+              available_composites[GetFuzzerContext()->RandomIndex(
+                  available_composites)];
+          composite_id = inst->result_id();
+
+          const auto* type =
+              GetIRContext()->get_type_mgr()->GetType(inst->type_id());
+          assert(type && "Composite instruction has invalid type id");
+
+          do {
+            uint32_t number_of_members = 0;
+
+            if (const auto* array_type = type->AsArray()) {
+              const auto* type_inst =
+                  GetIRContext()->get_def_use_mgr()->GetDef(inst->type_id());
+              assert(type_inst && "Type instruction must exist");
+
+              number_of_members =
+                  fuzzerutil::GetArraySize(*type_inst, GetIRContext());
+              type = array_type->element_type();
+            } else if (const auto* vector_type = type->AsVector()) {
+              number_of_members = vector_type->element_count();
+              type = vector_type->element_type();
+            } else if (const auto* matrix_type = type->AsMatrix()) {
+              number_of_members = matrix_type->element_count();
+              type = matrix_type->element_type();
+            } else if (const auto* struct_type = type->AsStruct()) {
+              number_of_members =
+                  static_cast<uint32_t>(struct_type->element_types().size());
+              // The next value of |type| will be assigned when we know the
+              // index of the OpTypeStruct's operand.
+            } else {
+              assert(false && "|inst| is not a composite");
+              return;
+            }
+
+            if (number_of_members == 0) {
+              return;
+            }
+
+            indices.push_back(
+                GetFuzzerContext()->GetRandomCompositeExtractIndex(
+                    number_of_members));
+
+            if (const auto* struct_type = type->AsStruct()) {
+              type = struct_type->element_types()[indices.back()];
+            }
+          } while (fuzzerutil::IsCompositeType(type) &&
+                   GetFuzzerContext()->ChoosePercentage(
+                       GetFuzzerContext()
+                           ->GetChanceOfGoingDeeperToExtractComposite()));
+        } else {
+          const auto* dd = available_synonyms[GetFuzzerContext()->RandomIndex(
+              available_synonyms)];
+
+          composite_id = dd->object();
+          indices.assign(dd->index().begin(), dd->index().end());
+        }
+
+        assert(composite_id != 0 && !indices.empty() &&
+               "Composite object should have been chosen correctly");
+
+        transformations.emplace_back(instruction_descriptor,
+                                     GetFuzzerContext()->GetFreshId(),
+                                     composite_id, indices);
+      });
+
+  for (const auto& transformation : transformations) {
+    ApplyTransformation(transformation);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_composite_extract.h b/source/fuzz/fuzzer_pass_add_composite_extract.h
new file mode 100644
index 0000000..8bcb825
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_extract.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_EXTRACT_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_EXTRACT_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly decides whether to add OpCompositeExtract before some instruction
+// in the module.
+class FuzzerPassAddCompositeExtract : public FuzzerPass {
+ public:
+  FuzzerPassAddCompositeExtract(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddCompositeExtract() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_EXTRACT_H_
diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.cpp b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp
new file mode 100644
index 0000000..e58c754
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_inserts.cpp
@@ -0,0 +1,234 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/transformation_composite_insert.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddCompositeInserts::FuzzerPassAddCompositeInserts(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddCompositeInserts::~FuzzerPassAddCompositeInserts() = default;
+
+void FuzzerPassAddCompositeInserts::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator instruction_iterator,
+             const protobufs::InstructionDescriptor& instruction_descriptor)
+          -> void {
+        assert(instruction_iterator->opcode() ==
+                   instruction_descriptor.target_instruction_opcode() &&
+               "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 adding an OpCompositeInsert
+        // instruction.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfAddingCompositeInsert())) {
+          return;
+        }
+
+        // It must be possible to insert an OpCompositeInsert instruction
+        // before |instruction_iterator|.
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
+                SpvOpCompositeInsert, instruction_iterator)) {
+          return;
+        }
+
+        // Look for available values that have composite type.
+        std::vector<opt::Instruction*> available_composites =
+            FindAvailableInstructions(
+                function, block, instruction_iterator,
+                [instruction_descriptor](
+                    opt::IRContext* ir_context,
+                    opt::Instruction* instruction) -> bool {
+                  // |instruction| must be a supported instruction of composite
+                  // type.
+                  if (!TransformationCompositeInsert::
+                          IsCompositeInstructionSupported(ir_context,
+                                                          instruction)) {
+                    return false;
+                  }
+
+                  auto instruction_type = ir_context->get_type_mgr()->GetType(
+                      instruction->type_id());
+
+                  // No components of the composite can have type
+                  // OpTypeRuntimeArray.
+                  if (ContainsRuntimeArray(*instruction_type)) {
+                    return false;
+                  }
+
+                  // No components of the composite can be pointers.
+                  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3658):
+                  //       Structs can have components of pointer type.
+                  //       FindOrCreateZeroConstant cannot be called on a
+                  //       pointer. We ignore pointers for now. Consider adding
+                  //       support for pointer types.
+                  if (ContainsPointer(*instruction_type)) {
+                    return false;
+                  }
+
+                  return true;
+                });
+
+        // If there are no available values, then return.
+        if (available_composites.empty()) {
+          return;
+        }
+
+        // Choose randomly one available composite value.
+        auto available_composite =
+            available_composites[GetFuzzerContext()->RandomIndex(
+                available_composites)];
+
+        // Take a random component of the chosen composite value. If the chosen
+        // component is itself a composite, then randomly decide whether to take
+        // its component and repeat.
+        uint32_t current_node_type_id = available_composite->type_id();
+        std::vector<uint32_t> path_to_replaced;
+        while (true) {
+          auto current_node_type_inst =
+              GetIRContext()->get_def_use_mgr()->GetDef(current_node_type_id);
+          uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex(
+              *current_node_type_inst, GetIRContext());
+
+          // If the composite is empty, then end the iteration.
+          if (num_of_components == 0) {
+            break;
+          }
+          uint32_t one_selected_index =
+              GetFuzzerContext()->GetRandomIndexForCompositeInsert(
+                  num_of_components);
+
+          // Construct a final index by appending the current index.
+          path_to_replaced.push_back(one_selected_index);
+          current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
+              GetIRContext(), current_node_type_id, one_selected_index);
+
+          // If the component is not a composite then end the iteration.
+          if (!fuzzerutil::IsCompositeType(
+                  GetIRContext()->get_type_mgr()->GetType(
+                      current_node_type_id))) {
+            break;
+          }
+
+          // If the component is a composite, but we decide not to go deeper,
+          // then end the iteration.
+          if (!GetFuzzerContext()->ChoosePercentage(
+                  GetFuzzerContext()
+                      ->GetChanceOfGoingDeeperToInsertInComposite())) {
+            break;
+          }
+        }
+
+        // Look for available objects that have the type id
+        // |current_node_type_id| and can be inserted.
+        std::vector<opt::Instruction*> available_objects =
+            FindAvailableInstructions(
+                function, block, instruction_iterator,
+                [instruction_descriptor, current_node_type_id](
+                    opt::IRContext* /*unused*/,
+                    opt::Instruction* instruction) -> bool {
+                  if (instruction->result_id() == 0 ||
+                      instruction->type_id() == 0) {
+                    return false;
+                  }
+                  if (instruction->type_id() != current_node_type_id) {
+                    return false;
+                  }
+                  return true;
+                });
+
+        // If there are no objects of the specific type available, check if
+        // FindOrCreateZeroConstant can be called and create a zero constant of
+        // this type.
+        uint32_t available_object_id;
+        if (available_objects.empty()) {
+          if (!fuzzerutil::CanCreateConstant(GetIRContext(),
+                                             current_node_type_id)) {
+            return;
+          }
+          available_object_id =
+              FindOrCreateZeroConstant(current_node_type_id, false);
+        } else {
+          available_object_id =
+              available_objects[GetFuzzerContext()->RandomIndex(
+                                    available_objects)]
+                  ->result_id();
+        }
+        auto new_result_id = GetFuzzerContext()->GetFreshId();
+
+        // Insert an OpCompositeInsert instruction which copies
+        // |available_composite| and in the copy inserts the object
+        // of type |available_object_id| at index |index_to_replace|.
+        ApplyTransformation(TransformationCompositeInsert(
+            instruction_descriptor, new_result_id,
+            available_composite->result_id(), available_object_id,
+            path_to_replaced));
+      });
+}
+
+bool FuzzerPassAddCompositeInserts::ContainsPointer(
+    const opt::analysis::Type& type) {
+  switch (type.kind()) {
+    case opt::analysis::Type::kPointer:
+      return true;
+    case opt::analysis::Type::kArray:
+      return ContainsPointer(*type.AsArray()->element_type());
+    case opt::analysis::Type::kMatrix:
+      return ContainsPointer(*type.AsMatrix()->element_type());
+    case opt::analysis::Type::kVector:
+      return ContainsPointer(*type.AsVector()->element_type());
+    case opt::analysis::Type::kStruct:
+      return std::any_of(type.AsStruct()->element_types().begin(),
+                         type.AsStruct()->element_types().end(),
+                         [](const opt::analysis::Type* element_type) {
+                           return ContainsPointer(*element_type);
+                         });
+    default:
+      return false;
+  }
+}
+
+bool FuzzerPassAddCompositeInserts::ContainsRuntimeArray(
+    const opt::analysis::Type& type) {
+  switch (type.kind()) {
+    case opt::analysis::Type::kRuntimeArray:
+      return true;
+    case opt::analysis::Type::kStruct:
+      // If any component of a struct is of type OpTypeRuntimeArray, return
+      // true.
+      return std::any_of(type.AsStruct()->element_types().begin(),
+                         type.AsStruct()->element_types().end(),
+                         [](const opt::analysis::Type* element_type) {
+                           return ContainsRuntimeArray(*element_type);
+                         });
+    default:
+      return false;
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_composite_inserts.h b/source/fuzz/fuzzer_pass_add_composite_inserts.h
new file mode 100644
index 0000000..c4f5103
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_composite_inserts.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that randomly adds new OpCompositeInsert instructions to
+// available values that have the composite type.
+class FuzzerPassAddCompositeInserts : public FuzzerPass {
+ public:
+  FuzzerPassAddCompositeInserts(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddCompositeInserts();
+  void Apply() override;
+
+  // Checks if any component of a composite is a pointer.
+  static bool ContainsPointer(const opt::analysis::Type& type);
+
+  // Checks if any component of a composite has type OpTypeRuntimeArray.
+  static bool ContainsRuntimeArray(const opt::analysis::Type& type);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_COMPOSITE_INSERTS_H_
diff --git a/source/fuzz/fuzzer_pass_add_composite_types.cpp b/source/fuzz/fuzzer_pass_add_composite_types.cpp
index 653d784..c4d8d1c 100644
--- a/source/fuzz/fuzzer_pass_add_composite_types.cpp
+++ b/source/fuzz/fuzzer_pass_add_composite_types.cpp
@@ -120,10 +120,15 @@
       case SpvOpTypeFloat:
       case SpvOpTypeInt:
       case SpvOpTypeMatrix:
-      case SpvOpTypeStruct:
       case SpvOpTypeVector:
         candidates.push_back(inst.result_id());
         break;
+      case SpvOpTypeStruct: {
+        if (!fuzzerutil::MembersHaveBuiltInDecoration(GetIRContext(),
+                                                      inst.result_id())) {
+          candidates.push_back(inst.result_id());
+        }
+      } break;
       default:
         break;
     }
diff --git a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
index cf4ecee..e726c63 100644
--- a/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_breaks.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
+
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/transformation_add_dead_break.h"
 #include "source/opt/ir_context.h"
@@ -66,11 +67,15 @@
         // anything.
         if (!block.IsSuccessor(merge_block)) {
           merge_block->ForEachPhiInst([this, &phi_ids](opt::Instruction* phi) {
-            // Add an additional operand for OpPhi instruction.
-            //
-            // We mark the constant as irrelevant so that we can replace it with
-            // a more interesting value later.
-            phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id(), true));
+            // Add an additional operand for OpPhi instruction.  Use a constant
+            // if possible, and an undef otherwise.
+            if (fuzzerutil::CanCreateConstant(GetIRContext(), phi->type_id())) {
+              // We mark the constant as irrelevant so that we can replace it
+              // with a more interesting value later.
+              phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id(), true));
+            } else {
+              phi_ids.push_back(FindOrCreateGlobalUndef(phi->type_id()));
+            }
           });
         }
 
diff --git a/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
index 61a7c6d..24617ae 100644
--- a/source/fuzz/fuzzer_pass_add_dead_continues.cpp
+++ b/source/fuzz/fuzzer_pass_add_dead_continues.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/fuzz/fuzzer_pass_add_dead_continues.h"
+
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/transformation_add_dead_continue.h"
 #include "source/opt/ir_context.h"
@@ -57,11 +58,15 @@
       // If this is the case, we don't need to do anything.
       if (!block.IsSuccessor(continue_block)) {
         continue_block->ForEachPhiInst([this, &phi_ids](opt::Instruction* phi) {
-          // Add an additional operand for OpPhi instruction.
-          //
-          // We mark the constant as irrelevant so that we can replace it with a
-          // more interesting value later.
-          phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id(), true));
+          // Add an additional operand for OpPhi instruction.  Use a constant
+          // if possible, and an undef otherwise.
+          if (fuzzerutil::CanCreateConstant(GetIRContext(), phi->type_id())) {
+            // We mark the constant as irrelevant so that we can replace it with
+            // a more interesting value later.
+            phi_ids.push_back(FindOrCreateZeroConstant(phi->type_id(), true));
+          } else {
+            phi_ids.push_back(FindOrCreateGlobalUndef(phi->type_id()));
+          }
         });
       }
 
diff --git a/source/fuzz/fuzzer_pass_add_equation_instructions.cpp b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
index dc811e6..6376c9f 100644
--- a/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
+++ b/source/fuzz/fuzzer_pass_add_equation_instructions.cpp
@@ -21,6 +21,26 @@
 
 namespace spvtools {
 namespace fuzz {
+namespace {
+
+bool IsBitWidthSupported(opt::IRContext* ir_context, uint32_t bit_width) {
+  switch (bit_width) {
+    case 32:
+      return true;
+    case 64:
+      return ir_context->get_feature_mgr()->HasCapability(
+                 SpvCapabilityFloat64) &&
+             ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64);
+    case 16:
+      return ir_context->get_feature_mgr()->HasCapability(
+                 SpvCapabilityFloat16) &&
+             ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16);
+    default:
+      return false;
+  }
+}
+
+}  // namespace
 
 FuzzerPassAddEquationInstructions::FuzzerPassAddEquationInstructions(
     opt::IRContext* ir_context, TransformationContext* transformation_context,
@@ -57,7 +77,8 @@
         std::vector<opt::Instruction*> available_instructions =
             FindAvailableInstructions(
                 function, block, inst_it,
-                [this](opt::IRContext*, opt::Instruction* instruction) -> bool {
+                [this](opt::IRContext* /*unused*/,
+                       opt::Instruction* instruction) -> bool {
                   return instruction->result_id() && instruction->type_id() &&
                          instruction->opcode() != SpvOpUndef &&
                          !GetTransformationContext()
@@ -76,8 +97,22 @@
           switch (opcode) {
             case SpvOpConvertSToF:
             case SpvOpConvertUToF: {
-              auto candidate_instructions =
-                  GetIntegerInstructions(available_instructions);
+              std::vector<const opt::Instruction*> candidate_instructions;
+              for (const auto* inst :
+                   GetIntegerInstructions(available_instructions)) {
+                const auto* type =
+                    GetIRContext()->get_type_mgr()->GetType(inst->type_id());
+                assert(type && "|inst| has invalid type");
+
+                if (const auto* vector_type = type->AsVector()) {
+                  type = vector_type->element_type();
+                }
+
+                if (IsBitWidthSupported(GetIRContext(),
+                                        type->AsInteger()->width())) {
+                  candidate_instructions.push_back(inst);
+                }
+              }
 
               if (candidate_instructions.empty()) {
                 break;
@@ -93,10 +128,15 @@
 
               // Make sure a result type exists in the module.
               if (const auto* vector = type->AsVector()) {
+                // We store element count in a separate variable since the
+                // call FindOrCreate* functions below might invalidate
+                // |vector| pointer.
+                const auto element_count = vector->element_count();
+
                 FindOrCreateVectorType(
                     FindOrCreateFloatType(
                         vector->element_type()->AsInteger()->width()),
-                    vector->element_count());
+                    element_count);
               } else {
                 FindOrCreateFloatType(type->AsInteger()->width());
               }
@@ -107,20 +147,8 @@
               return;
             }
             case SpvOpBitcast: {
-              std::vector<const opt::Instruction*> candidate_instructions;
-              for (const auto* inst : available_instructions) {
-                const auto* type =
-                    GetIRContext()->get_type_mgr()->GetType(inst->type_id());
-                assert(type && "Instruction has invalid type");
-                if ((type->AsVector() &&
-                     (type->AsVector()->element_type()->AsInteger() ||
-                      type->AsVector()->element_type()->AsFloat())) ||
-                    type->AsInteger() || type->AsFloat()) {
-                  // We support OpBitcast for only scalars or vectors of
-                  // numerical type.
-                  candidate_instructions.push_back(inst);
-                }
-              }
+              const auto candidate_instructions =
+                  GetNumericalInstructions(available_instructions);
 
               if (!candidate_instructions.empty()) {
                 const auto* operand_inst =
@@ -138,6 +166,11 @@
                 //  is that they must have the same number of bits. Consider
                 //  improving the code below to support this in full.
                 if (const auto* vector = operand_type->AsVector()) {
+                  // We store element count in a separate variable since the
+                  // call FindOrCreate* functions below might invalidate
+                  // |vector| pointer.
+                  const auto element_count = vector->element_count();
+
                   uint32_t element_type_id;
                   if (const auto* int_type =
                           vector->element_type()->AsInteger()) {
@@ -150,8 +183,7 @@
                         GetFuzzerContext()->ChooseEven());
                   }
 
-                  FindOrCreateVectorType(element_type_id,
-                                         vector->element_count());
+                  FindOrCreateVectorType(element_type_id, element_count);
                 } else if (const auto* int_type = operand_type->AsInteger()) {
                   FindOrCreateFloatType(int_type->width());
                 } else {
@@ -347,5 +379,36 @@
   return result;
 }
 
+std::vector<opt::Instruction*>
+FuzzerPassAddEquationInstructions::GetNumericalInstructions(
+    const std::vector<opt::Instruction*>& instructions) const {
+  std::vector<opt::Instruction*> result;
+
+  for (auto* inst : instructions) {
+    const auto* type = GetIRContext()->get_type_mgr()->GetType(inst->type_id());
+    assert(type && "Instruction has invalid type");
+
+    if (const auto* vector_type = type->AsVector()) {
+      type = vector_type->element_type();
+    }
+
+    if (!type->AsInteger() && !type->AsFloat()) {
+      // Only numerical scalars or vectors of numerical components are
+      // supported.
+      continue;
+    }
+
+    if (!IsBitWidthSupported(GetIRContext(), type->AsInteger()
+                                                 ? type->AsInteger()->width()
+                                                 : type->AsFloat()->width())) {
+      continue;
+    }
+
+    result.push_back(inst);
+  }
+
+  return result;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_equation_instructions.h b/source/fuzz/fuzzer_pass_add_equation_instructions.h
index 8328b6b..9ce581e 100644
--- a/source/fuzz/fuzzer_pass_add_equation_instructions.h
+++ b/source/fuzz/fuzzer_pass_add_equation_instructions.h
@@ -51,6 +51,14 @@
   std::vector<opt::Instruction*> GetBooleanInstructions(
       const std::vector<opt::Instruction*>& instructions) const;
 
+  // Yields those instructions in |instructions| that have a scalar numerical or
+  // a vector of numerical components type. Only 16, 32 and 64-bit numericals
+  // are supported if both OpTypeInt and OpTypeFloat instructions can be created
+  // with the specified width (e.g. for 16-bit types both Float16 and Int16
+  // capabilities must be present).
+  std::vector<opt::Instruction*> GetNumericalInstructions(
+      const std::vector<opt::Instruction*>& instructions) const;
+
   // Requires that |instructions| are scalars or vectors of some type.  Returns
   // only those instructions whose width is |width|. If |width| is 1 this means
   // the scalars.
diff --git a/source/fuzz/fuzzer_pass_add_function_calls.cpp b/source/fuzz/fuzzer_pass_add_function_calls.cpp
index b6f4c85..7400557 100644
--- a/source/fuzz/fuzzer_pass_add_function_calls.cpp
+++ b/source/fuzz/fuzzer_pass_add_function_calls.cpp
@@ -141,9 +141,13 @@
     assert(param_type && "Parameter has invalid type");
 
     if (!param_type->AsPointer()) {
-      // We mark the constant as irrelevant so that we can replace it with a
-      // more interesting value later.
-      result.push_back(FindOrCreateZeroConstant(param->type_id(), true));
+      if (fuzzerutil::CanCreateConstant(GetIRContext(), param->type_id())) {
+        // We mark the constant as irrelevant so that we can replace it with a
+        // more interesting value later.
+        result.push_back(FindOrCreateZeroConstant(param->type_id(), true));
+      } else {
+        result.push_back(FindOrCreateGlobalUndef(param->type_id()));
+      }
       continue;
     }
 
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 313bb0d..3095bb6 100644
--- a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp
+++ b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp
@@ -184,7 +184,7 @@
          // FindOrCreateZeroConstant
          // %20 = OpConstant %4 0
          // %21 = OpConstantComposite %5 %20 %20
-         FindOrCreateZeroConstant(zero_constant_type_id, false)},
+         FindOrCreateZeroConstant(zero_constant_type_id, true)},
         MakeInstructionDescriptor(GetIRContext(), instruction),
         coordinate_with_unused_components_id));
 
diff --git a/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp b/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp
new file mode 100644
index 0000000..bdc3151
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loop_preheaders.cpp
@@ -0,0 +1,66 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_loop_preheader.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddLoopPreheaders::FuzzerPassAddLoopPreheaders(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddLoopPreheaders::~FuzzerPassAddLoopPreheaders() = default;
+
+void FuzzerPassAddLoopPreheaders::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    // Keep track of all the loop headers we want to add a preheader to.
+    std::vector<uint32_t> loop_header_ids_to_consider;
+    for (auto& block : function) {
+      // We only care about loop headers.
+      if (!block.IsLoopHeader()) {
+        continue;
+      }
+
+      // Randomly decide whether to consider this header.
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfAddingLoopPreheader())) {
+        continue;
+      }
+
+      // We exclude loop headers with just one predecessor (the back-edge block)
+      // because they are unreachable.
+      if (GetIRContext()->cfg()->preds(block.id()).size() < 2) {
+        continue;
+      }
+
+      loop_header_ids_to_consider.push_back(block.id());
+    }
+
+    for (uint32_t header_id : loop_header_ids_to_consider) {
+      // If not already present, add a preheader which is not also a loop
+      // header.
+      GetOrCreateSimpleLoopPreheader(header_id);
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_loop_preheaders.h b/source/fuzz/fuzzer_pass_add_loop_preheaders.h
new file mode 100644
index 0000000..a835056
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loop_preheaders.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that randomly adds simple loop preheaders to loops that do not
+// have one. A simple loop preheader is a block that:
+// - is the only out-of-loop predecessor of the header
+// - branches unconditionally to the header
+// - is not a loop header itself
+class FuzzerPassAddLoopPreheaders : public FuzzerPass {
+ public:
+  FuzzerPassAddLoopPreheaders(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddLoopPreheaders();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOP_PREHEADERS_H
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
new file mode 100644
index 0000000..31a5779
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.cpp
@@ -0,0 +1,258 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h"
+
+#include "source/fuzz/call_graph.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+uint32_t kMaxNestingDepth = 4;
+}  // namespace
+
+FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+    FuzzerPassAddLoopsToCreateIntConstantSynonyms(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+    ~FuzzerPassAddLoopsToCreateIntConstantSynonyms() = default;
+
+void FuzzerPassAddLoopsToCreateIntConstantSynonyms::Apply() {
+  std::vector<uint32_t> constants;
+
+  // Choose the constants for which to create synonyms.
+  for (auto constant_def : GetIRContext()->GetConstants()) {
+    // Randomly decide whether to consider this constant.
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfCreatingIntSynonymsUsingLoops())) {
+      continue;
+    }
+
+    auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
+        constant_def->result_id());
+
+    // We do not consider irrelevant constants
+    if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+            constant_def->result_id())) {
+      continue;
+    }
+
+    // We only consider integer constants (scalar or vector).
+    if (!constant->AsIntConstant() &&
+        !(constant->AsVectorConstant() &&
+          constant->AsVectorConstant()->component_type()->AsInteger())) {
+      continue;
+    }
+
+    constants.push_back(constant_def->result_id());
+  }
+
+  std::vector<uint32_t> blocks;
+
+  // Get a list of all the blocks before which we can add a loop creating a new
+  // synonym. We cannot apply the transformation while iterating over the
+  // module, because we are going to add new blocks.
+  for (auto& function : *GetIRContext()->module()) {
+    // Consider all non-dead blocks reachable from the first block of the
+    // function.
+    GetIRContext()->cfg()->ForEachBlockInPostOrder(
+        &*function.begin(), [this, &blocks](opt::BasicBlock* block) {
+          if (!GetTransformationContext()->GetFactManager()->BlockIsDead(
+                  block->id())) {
+            blocks.push_back(block->id());
+          }
+        });
+  }
+
+  // Make sure that the module has an OpTypeBool instruction, and 32-bit signed
+  // integer constants 0 and 1, adding them if necessary.
+  FindOrCreateBoolType();
+  FindOrCreateIntegerConstant({0}, 32, true, false);
+  FindOrCreateIntegerConstant({1}, 32, true, false);
+
+  // Compute the call graph. We can use this for any further computation, since
+  // we are not adding or removing functions or function calls.
+  auto call_graph = CallGraph(GetIRContext());
+
+  // Consider each constant and each block.
+  for (uint32_t constant_id : constants) {
+    // Choose one of the blocks.
+    uint32_t block_id = blocks[GetFuzzerContext()->RandomIndex(blocks)];
+
+    // Adjust the block so that the transformation can be applied.
+    auto block = GetIRContext()->get_instr_block(block_id);
+
+    // If the block is a loop header, add a simple preheader. We can do this
+    // because we have excluded all the non-reachable headers.
+    if (block->IsLoopHeader()) {
+      block = GetOrCreateSimpleLoopPreheader(block->id());
+      block_id = block->id();
+    }
+
+    assert(!block->IsLoopHeader() &&
+           "The block cannot be a loop header at this point.");
+
+    // If the block is a merge block, a continue block or it does not have
+    // exactly 1 predecessor, split it after any OpPhi or OpVariable
+    // instructions.
+    if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id()) ||
+        GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock(
+            block->id()) ||
+        GetIRContext()->cfg()->preds(block->id()).size() != 1) {
+      block = SplitBlockAfterOpPhiOrOpVariable(block->id());
+      block_id = block->id();
+    }
+
+    // Randomly decide the values for the number of iterations and the step
+    // value, and compute the initial value accordingly.
+
+    // The maximum number of iterations depends on the maximum possible loop
+    // nesting depth of the block, computed interprocedurally, i.e. also
+    // considering the possibility that the enclosing function is called inside
+    // a loop. It is:
+    // - 1 if the nesting depth is >= kMaxNestingDepth
+    // - 2^(kMaxNestingDepth - nesting_depth) otherwise
+    uint32_t max_nesting_depth =
+        call_graph.GetMaxCallNestingDepth(block->GetParent()->result_id()) +
+        GetIRContext()->GetStructuredCFGAnalysis()->LoopNestingDepth(
+            block->id());
+    uint32_t num_iterations =
+        max_nesting_depth >= kMaxNestingDepth
+            ? 1
+            : GetFuzzerContext()->GetRandomNumberOfLoopIterations(
+                  1u << (kMaxNestingDepth - max_nesting_depth));
+
+    // Find or create the corresponding constant containing the number of
+    // iterations.
+    uint32_t num_iterations_id =
+        FindOrCreateIntegerConstant({num_iterations}, 32, true, false);
+
+    // Find the other constants.
+    // We use 64-bit values and then use the bits that we need. We find the
+    // step value (S) randomly and then compute the initial value (I) using
+    // the equation I = C + S*N.
+    uint32_t initial_value_id = 0;
+    uint32_t step_value_id = 0;
+
+    // Get the content of the existing constant.
+    const auto constant =
+        GetIRContext()->get_constant_mgr()->FindDeclaredConstant(constant_id);
+    const auto constant_type_id =
+        GetIRContext()->get_def_use_mgr()->GetDef(constant_id)->type_id();
+
+    if (constant->AsIntConstant()) {
+      // The constant is a scalar integer.
+
+      std::tie(initial_value_id, step_value_id) =
+          FindSuitableStepAndInitialValueConstants(
+              constant->GetZeroExtendedValue(),
+              constant->type()->AsInteger()->width(),
+              constant->type()->AsInteger()->IsSigned(), num_iterations);
+    } else {
+      // The constant is a vector of integers.
+      assert(constant->AsVectorConstant() &&
+             constant->AsVectorConstant()->component_type()->AsInteger() &&
+             "If the program got here, the constant should be a vector of "
+             "integers.");
+
+      // Find a constant for each component of the initial value and the step
+      // values.
+      std::vector<uint32_t> initial_value_component_ids;
+      std::vector<uint32_t> step_value_component_ids;
+
+      // Get the value, width and signedness of the components.
+      std::vector<uint64_t> component_values;
+      for (auto component : constant->AsVectorConstant()->GetComponents()) {
+        component_values.push_back(component->GetZeroExtendedValue());
+      }
+      uint32_t bit_width =
+          constant->AsVectorConstant()->component_type()->AsInteger()->width();
+      uint32_t is_signed = constant->AsVectorConstant()
+                               ->component_type()
+                               ->AsInteger()
+                               ->IsSigned();
+
+      for (uint64_t component_val : component_values) {
+        uint32_t initial_val_id;
+        uint32_t step_val_id;
+        std::tie(initial_val_id, step_val_id) =
+            FindSuitableStepAndInitialValueConstants(component_val, bit_width,
+                                                     is_signed, num_iterations);
+        initial_value_component_ids.push_back(initial_val_id);
+        step_value_component_ids.push_back(step_val_id);
+      }
+
+      // Find or create the vector constants.
+      initial_value_id = FindOrCreateCompositeConstant(
+          initial_value_component_ids, constant_type_id, false);
+      step_value_id = FindOrCreateCompositeConstant(step_value_component_ids,
+                                                    constant_type_id, false);
+    }
+
+    assert(initial_value_id && step_value_id &&
+           "|initial_value_id| and |step_value_id| should have been defined.");
+
+    // Randomly decide whether to have two blocks (or just one) in the new
+    // loop.
+    uint32_t additional_block_id =
+        GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()
+                ->GetChanceOfHavingTwoBlocksInLoopToCreateIntSynonym())
+            ? GetFuzzerContext()->GetFreshId()
+            : 0;
+
+    // Add the loop and create the synonym.
+    ApplyTransformation(TransformationAddLoopToCreateIntConstantSynonym(
+        constant_id, initial_value_id, step_value_id, num_iterations_id,
+        block_id, GetFuzzerContext()->GetFreshId(),
+        GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+        GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+        GetFuzzerContext()->GetFreshId(), GetFuzzerContext()->GetFreshId(),
+        additional_block_id));
+  }
+}
+
+std::pair<uint32_t, uint32_t> FuzzerPassAddLoopsToCreateIntConstantSynonyms::
+    FindSuitableStepAndInitialValueConstants(uint64_t constant_val,
+                                             uint32_t bit_width, bool is_signed,
+                                             uint32_t num_iterations) {
+  // Choose the step value randomly and compute the initial value accordingly.
+  // The result of |initial_value| could overflow, but this is OK, since
+  // the transformation takes overflows into consideration (the equation still
+  // holds as long as the last |bit_width| bits of C and of (I-S*N) match).
+  uint64_t step_value =
+      GetFuzzerContext()->GetRandomValueForStepConstantInLoop();
+  uint64_t initial_value = constant_val + step_value * num_iterations;
+
+  uint32_t initial_val_id = FindOrCreateIntegerConstant(
+      fuzzerutil::IntToWords(initial_value, bit_width, is_signed), bit_width,
+      is_signed, false);
+
+  uint32_t step_val_id = FindOrCreateIntegerConstant(
+      fuzzerutil::IntToWords(step_value, bit_width, is_signed), bit_width,
+      is_signed, false);
+
+  return {initial_val_id, step_val_id};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
\ No newline at end of file
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
new file mode 100644
index 0000000..ee98c4e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that adds synonyms for integer, scalar or vector, constants, by
+// adding loops that compute the same value by subtracting a value S from an
+// initial value I, and for N times, so that C = I - S*N.
+class FuzzerPassAddLoopsToCreateIntConstantSynonyms : public FuzzerPass {
+ public:
+  FuzzerPassAddLoopsToCreateIntConstantSynonyms(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddLoopsToCreateIntConstantSynonyms();
+
+  void Apply() override;
+
+ private:
+  // Returns a pair (initial_val_id, step_val_id) such that both ids are
+  // integer scalar constants of the same type as the scalar integer constant
+  // identified by the given |constant_val|, |bit_width| and signedness, and
+  // such that, if I is the value of initial_val_id, S is the value of
+  // step_val_id and C is the value of the constant, the equation (C = I - S *
+  // num_iterations) holds, (only considering the last |bit_width| bits of each
+  // constant).
+  std::pair<uint32_t, uint32_t> FindSuitableStepAndInitialValueConstants(
+      uint64_t constant_val, uint32_t bit_width, bool is_signed,
+      uint32_t num_iterations);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_LOOPS_TO_CREATE_INT_CONSTANT_SYNONYMS_H_
diff --git a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
index f32e5bc..c0c1e09 100644
--- a/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
+++ b/source/fuzz/fuzzer_pass_add_no_contraction_decorations.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_
-#define SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -36,4 +36,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_NO_CONTRACTION_DECORATIONS_H_
diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
new file mode 100644
index 0000000..2b339ca
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.cpp
@@ -0,0 +1,312 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_opphi_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddOpPhiSynonyms::~FuzzerPassAddOpPhiSynonyms() = default;
+
+void FuzzerPassAddOpPhiSynonyms::Apply() {
+  // Get a list of synonymous ids with the same type that can be used in the
+  // same OpPhi instruction.
+  auto equivalence_classes = GetIdEquivalenceClasses();
+
+  // Make a list of references, to avoid copying sets unnecessarily.
+  std::vector<std::set<uint32_t>*> equivalence_class_pointers;
+  for (auto& set : equivalence_classes) {
+    equivalence_class_pointers.push_back(&set);
+  }
+
+  // Keep a list of transformations to apply at the end.
+  std::vector<TransformationAddOpPhiSynonym> transformations_to_apply;
+
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // Randomly decide whether to consider this block.
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) {
+        continue;
+      }
+
+      // The block must not be dead.
+      if (GetTransformationContext()->GetFactManager()->BlockIsDead(
+              block.id())) {
+        continue;
+      }
+
+      // The block must have at least one predecessor.
+      size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size();
+      if (num_preds == 0) {
+        continue;
+      }
+
+      std::set<uint32_t>* chosen_equivalence_class = nullptr;
+
+      if (num_preds > 1) {
+        // If the block has more than one predecessor, prioritise sets with at
+        // least 2 ids available at some predecessor.
+        chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
+            equivalence_class_pointers, block.id(), 2);
+      }
+
+      // If a set was not already chosen, choose one with at least one available
+      // id.
+      if (!chosen_equivalence_class) {
+        chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
+            equivalence_class_pointers, block.id(), 1);
+      }
+
+      // If no suitable set was found, we cannot apply the transformation to
+      // this block.
+      if (!chosen_equivalence_class) {
+        continue;
+      }
+
+      // Initialise the map from predecessor labels to ids.
+      std::map<uint32_t, uint32_t> preds_to_ids;
+
+      // Keep track of the ids used and of the id of a predecessor with at least
+      // two ids to choose from. This is to ensure that, if possible, at least
+      // two distinct ids will be used.
+      std::set<uint32_t> ids_chosen;
+      uint32_t pred_with_alternatives = 0;
+
+      // Choose an id for each predecessor.
+      for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) {
+        auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id);
+        assert(!suitable_ids.empty() &&
+               "We must be able to find at least one suitable id because the "
+               "equivalence class was chosen among suitable ones.");
+
+        // If this predecessor has more than one id to choose from and it is the
+        // first one of this kind that we found, remember its id.
+        if (suitable_ids.size() > 1 && !pred_with_alternatives) {
+          pred_with_alternatives = pred_id;
+        }
+
+        uint32_t chosen_id =
+            suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)];
+
+        // Add this id to the set of ids chosen.
+        ids_chosen.emplace(chosen_id);
+
+        // Add the pair (predecessor, chosen id) to the map.
+        preds_to_ids[pred_id] = chosen_id;
+      }
+
+      // If:
+      // - the block has more than one predecessor
+      // - at least one predecessor has more than one alternative
+      // - the same id has been chosen by all the predecessors
+      // then choose another one for the predecessor with more than one
+      // alternative.
+      if (num_preds > 1 && pred_with_alternatives != 0 &&
+          ids_chosen.size() == 1) {
+        auto suitable_ids =
+            GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives);
+        uint32_t chosen_id =
+            GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
+        if (chosen_id == preds_to_ids[pred_with_alternatives]) {
+          chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
+        }
+
+        preds_to_ids[pred_with_alternatives] = chosen_id;
+      }
+
+      // Add the transformation to the list of transformations to apply.
+      transformations_to_apply.emplace_back(block.id(), preds_to_ids,
+                                            GetFuzzerContext()->GetFreshId());
+    }
+  }
+
+  // Apply the transformations.
+  for (const auto& transformation : transformations_to_apply) {
+    ApplyTransformation(transformation);
+  }
+}
+
+std::vector<std::set<uint32_t>>
+FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() {
+  std::vector<std::set<uint32_t>> id_equivalence_classes;
+
+  // Keep track of all the ids that have already be assigned to a class.
+  std::set<uint32_t> already_in_a_class;
+
+  for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
+    // Exclude ids that have already been assigned to a class.
+    if (already_in_a_class.count(pair.first)) {
+      continue;
+    }
+
+    // Exclude irrelevant ids.
+    if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+            pair.first)) {
+      continue;
+    }
+
+    // Exclude ids having a type that is not allowed by the transformation.
+    if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
+            GetIRContext(), pair.second->type_id())) {
+      continue;
+    }
+
+    // Exclude OpFunction and OpUndef instructions, because:
+    // - OpFunction does not yield a value;
+    // - OpUndef yields an undefined value at each use, so it should never be a
+    //   synonym of another id.
+    if (pair.second->opcode() == SpvOpFunction ||
+        pair.second->opcode() == SpvOpUndef) {
+      continue;
+    }
+
+    // We need a new equivalence class for this id.
+    std::set<uint32_t> new_equivalence_class;
+
+    // Add this id to the class.
+    new_equivalence_class.emplace(pair.first);
+    already_in_a_class.emplace(pair.first);
+
+    // Add all the synonyms with the same type to this class.
+    for (auto synonym :
+         GetTransformationContext()->GetFactManager()->GetSynonymsForId(
+             pair.first)) {
+      // The synonym must be a plain id - it cannot be an indexed access into a
+      // composite.
+      if (synonym->index_size() > 0) {
+        continue;
+      }
+
+      // The synonym must not be irrelevant.
+      if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+              synonym->object())) {
+        continue;
+      }
+
+      auto synonym_def =
+          GetIRContext()->get_def_use_mgr()->GetDef(synonym->object());
+      // The synonym must exist and have the same type as the id we are
+      // considering.
+      if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) {
+        continue;
+      }
+
+      // We can add this synonym to the new equivalence class.
+      new_equivalence_class.emplace(synonym->object());
+      already_in_a_class.emplace(synonym->object());
+    }
+
+    // Add the new equivalence class to the list of equivalence classes.
+    id_equivalence_classes.emplace_back(std::move(new_equivalence_class));
+  }
+
+  return id_equivalence_classes;
+}
+
+bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock(
+    const std::set<uint32_t>& equivalence_class, uint32_t block_id,
+    uint32_t distinct_ids_required) {
+  bool at_least_one_id_for_each_pred = true;
+
+  // Keep a set of the suitable ids found.
+  std::set<uint32_t> suitable_ids_found;
+
+  // Loop through all the predecessors of the block.
+  for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) {
+    // Find the last instruction in the predecessor block.
+    auto last_instruction =
+        GetIRContext()->get_instr_block(pred_id)->terminator();
+
+    // Initially assume that there is not a suitable id for this predecessor.
+    bool at_least_one_suitable_id_found = false;
+    for (uint32_t id : equivalence_class) {
+      if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(),
+                                                     last_instruction, id)) {
+        // We have found a suitable id.
+        at_least_one_suitable_id_found = true;
+        suitable_ids_found.emplace(id);
+
+        // If we have already found enough distinct suitable ids, we don't need
+        // to check the remaining ones for this predecessor.
+        if (suitable_ids_found.size() >= distinct_ids_required) {
+          break;
+        }
+      }
+    }
+    // If no suitable id was found for this predecessor, this equivalence class
+    // is not suitable and we don't need to check the other predecessors.
+    if (!at_least_one_suitable_id_found) {
+      at_least_one_id_for_each_pred = false;
+      break;
+    }
+  }
+
+  // The equivalence class is suitable if at least one suitable id was found for
+  // each predecessor and we have found at least |distinct_ids_required|
+  // distinct suitable ids in general.
+  return at_least_one_id_for_each_pred &&
+         suitable_ids_found.size() >= distinct_ids_required;
+}
+
+std::vector<uint32_t> FuzzerPassAddOpPhiSynonyms::GetSuitableIds(
+    const std::set<uint32_t>& ids, uint32_t pred_id) {
+  // Initialise an empty vector of suitable ids.
+  std::vector<uint32_t> suitable_ids;
+
+  // Get the predecessor block.
+  auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id);
+
+  // Loop through the ids to find the suitable ones.
+  for (uint32_t id : ids) {
+    if (fuzzerutil::IdIsAvailableBeforeInstruction(
+            GetIRContext(), predecessor->terminator(), id)) {
+      suitable_ids.push_back(id);
+    }
+  }
+
+  return suitable_ids;
+}
+
+std::set<uint32_t>*
+FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly(
+    const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
+    uint32_t distinct_ids_required) {
+  auto remaining_candidates = candidates;
+  while (!remaining_candidates.empty()) {
+    // Choose one set randomly and return it if it is suitable.
+    auto chosen =
+        GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates);
+    if (EquivalenceClassIsSuitableForBlock(*chosen, block_id,
+                                           distinct_ids_required)) {
+      return chosen;
+    }
+  }
+
+  // No suitable sets were found.
+  return nullptr;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_opphi_synonyms.h b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
new file mode 100644
index 0000000..6c7d752
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_opphi_synonyms.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass to add OpPhi instructions which can take the values of ids that
+// have been marked as synonymous. This instruction will itself be marked as
+// 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;
+
+  void Apply() override;
+
+  // Computes the equivalence classes for the non-pointer and non-irrelevant ids
+  // in the module, where two ids are considered equivalent iff they have been
+  // declared synonymous and they have the same type.
+  std::vector<std::set<uint32_t>> GetIdEquivalenceClasses();
+
+  // Returns true iff |equivalence_class| contains at least
+  // |distinct_ids_required| ids so that all of these ids are available at the
+  // end of at least one predecessor of the block with label |block_id|.
+  // Assumes that the block has at least one predecessor.
+  bool EquivalenceClassIsSuitableForBlock(
+      const std::set<uint32_t>& equivalence_class, uint32_t block_id,
+      uint32_t distinct_ids_required);
+
+  // Returns a vector with the ids that are available to use at the end of the
+  // block with id |pred_id|, selected among the given |ids|. Assumes that
+  // |pred_id| is the label of a block and all ids in |ids| exist in the module.
+  std::vector<uint32_t> GetSuitableIds(const std::set<uint32_t>& ids,
+                                       uint32_t pred_id);
+
+ private:
+  // Randomly chooses one of the equivalence classes in |candidates|, so that it
+  // satisfies all of the following conditions:
+  // - For each of the predecessors of the |block_id| block, there is at least
+  //   one id in the chosen equivalence class that is available at the end of
+  //   it.
+  // - There are at least |distinct_ids_required| ids available at the end of
+  //   some predecessor.
+  // Returns nullptr if no equivalence class in |candidates| satisfies the
+  // requirements.
+  std::set<uint32_t>* MaybeFindSuitableEquivalenceClassRandomly(
+      const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
+      uint32_t distinct_ids_required);
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_OPPHI_SYNONYMS_H_
diff --git a/source/fuzz/fuzzer_pass_add_parameters.cpp b/source/fuzz/fuzzer_pass_add_parameters.cpp
index c5c9c33..35532e9 100644
--- a/source/fuzz/fuzzer_pass_add_parameters.cpp
+++ b/source/fuzz/fuzzer_pass_add_parameters.cpp
@@ -35,10 +35,8 @@
   // Compute type candidates for the new parameter.
   std::vector<uint32_t> type_candidates;
   for (const auto& type_inst : GetIRContext()->module()->GetTypes()) {
-    const auto* type =
-        GetIRContext()->get_type_mgr()->GetType(type_inst->result_id());
-    assert(type && "Type instruction is not registered in the type manager");
-    if (TransformationAddParameter::IsParameterTypeSupported(*type)) {
+    if (TransformationAddParameter::IsParameterTypeSupported(
+            GetIRContext(), type_inst->result_id())) {
       type_candidates.push_back(type_inst->result_id());
     }
   }
@@ -69,14 +67,66 @@
     auto num_new_parameters =
         GetFuzzerContext()->GetRandomNumberOfNewParameters(
             GetNumberOfParameters(function));
+
     for (uint32_t i = 0; i < num_new_parameters; ++i) {
+      auto current_type_id =
+          type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)];
+      auto current_type =
+          GetIRContext()->get_type_mgr()->GetType(current_type_id);
+      std::map<uint32_t, uint32_t> call_parameter_ids;
+
+      // Consider the case when a pointer type was selected.
+      if (current_type->kind() == opt::analysis::Type::kPointer) {
+        auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
+            GetIRContext(), current_type_id);
+        switch (storage_class) {
+          case SpvStorageClassFunction: {
+            // In every caller find or create a local variable that has the
+            // selected type.
+            for (auto* instr :
+                 fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
+              auto block = GetIRContext()->get_instr_block(instr);
+              auto function_id = block->GetParent()->result_id();
+              uint32_t variable_id =
+                  FindOrCreateLocalVariable(current_type_id, function_id, true);
+              call_parameter_ids[instr->result_id()] = variable_id;
+            }
+          } break;
+          case SpvStorageClassPrivate:
+          case SpvStorageClassWorkgroup: {
+            // If there exists at least one caller, find or create a global
+            // variable that has the selected type.
+            std::vector<opt::Instruction*> callers =
+                fuzzerutil::GetCallers(GetIRContext(), function.result_id());
+            if (!callers.empty()) {
+              uint32_t variable_id =
+                  FindOrCreateGlobalVariable(current_type_id, true);
+              for (auto* instr : callers) {
+                call_parameter_ids[instr->result_id()] = variable_id;
+              }
+            }
+          } break;
+          default:
+            break;
+        }
+      } else {
+        // If there exists at least one caller, find or create a zero constant
+        // that has the selected type.
+        std::vector<opt::Instruction*> callers =
+            fuzzerutil::GetCallers(GetIRContext(), function.result_id());
+        if (!callers.empty()) {
+          uint32_t constant_id =
+              FindOrCreateZeroConstant(current_type_id, true);
+          for (auto* instr :
+               fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
+            call_parameter_ids[instr->result_id()] = constant_id;
+          }
+        }
+      }
+
       ApplyTransformation(TransformationAddParameter(
           function.result_id(), GetFuzzerContext()->GetFreshId(),
-          // We mark the constant as irrelevant so that we can replace it with a
-          // more interesting value later.
-          FindOrCreateZeroConstant(
-              type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)],
-              true),
+          current_type_id, std::move(call_parameter_ids),
           GetFuzzerContext()->GetFreshId()));
     }
   }
diff --git a/source/fuzz/fuzzer_pass_add_relaxed_decorations.h b/source/fuzz/fuzzer_pass_add_relaxed_decorations.h
index dad5dfc..897b821 100644
--- a/source/fuzz/fuzzer_pass_add_relaxed_decorations.h
+++ b/source/fuzz/fuzzer_pass_add_relaxed_decorations.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H
-#define SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -36,4 +36,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_RELAXED_DECORATIONS_H_
diff --git a/source/fuzz/fuzzer_pass_add_synonyms.cpp b/source/fuzz/fuzzer_pass_add_synonyms.cpp
index f109174..2fa0700 100644
--- a/source/fuzz/fuzzer_pass_add_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_add_synonyms.cpp
@@ -36,6 +36,12 @@
       [this](opt::Function* function, opt::BasicBlock* block,
              opt::BasicBlock::iterator inst_it,
              const protobufs::InstructionDescriptor& instruction_descriptor) {
+        if (GetTransformationContext()->GetFactManager()->BlockIsDead(
+                block->id())) {
+          // Don't create synonyms in dead blocks.
+          return;
+        }
+
         // Skip |inst_it| if we can't insert anything above it. OpIAdd is just
         // a representative of some instruction that might be produced by the
         // transformation.
diff --git a/source/fuzz/fuzzer_pass_adjust_function_controls.h b/source/fuzz/fuzzer_pass_adjust_function_controls.h
index e20541b..5fdbe43 100644
--- a/source/fuzz/fuzzer_pass_adjust_function_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_function_controls.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
-#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -36,4 +36,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_FUNCTION_CONTROLS_H_
diff --git a/source/fuzz/fuzzer_pass_adjust_loop_controls.h b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
index ee5cd48..f133b2d 100644
--- a/source/fuzz/fuzzer_pass_adjust_loop_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_loop_controls.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
-#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -36,4 +36,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_LOOP_CONTROLS_H_
diff --git a/source/fuzz/fuzzer_pass_adjust_selection_controls.h b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
index 820b30d..910b40d 100644
--- a/source/fuzz/fuzzer_pass_adjust_selection_controls.h
+++ b/source/fuzz/fuzzer_pass_adjust_selection_controls.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
-#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -36,4 +36,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADJUST_SELECTION_CONTROLS_H_
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
index 2808ad5..38553d2 100644
--- a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp
@@ -60,7 +60,7 @@
           }
         });
 
-    for (auto& use : uses) {
+    for (const auto& use : uses) {
       auto use_inst = use.first;
       auto use_index = use.second;
       auto block_containing_use = GetIRContext()->get_instr_block(use_inst);
@@ -76,13 +76,14 @@
       // the index of the operand restricted to input operands only.
       uint32_t use_in_operand_index =
           fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
-      if (!TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
-              GetIRContext(), use_inst, use_in_operand_index)) {
+      if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(),
+                                          *GetTransformationContext(), use_inst,
+                                          use_in_operand_index)) {
         continue;
       }
 
       std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
-      for (auto& data_descriptor :
+      for (const auto* data_descriptor :
            GetTransformationContext()->GetFactManager()->GetSynonymsForId(
                id_with_known_synonyms)) {
         protobufs::DataDescriptor descriptor_for_this_id =
@@ -91,7 +92,12 @@
           // Exclude the fact that the id is synonymous with itself.
           continue;
         }
-        synonyms_to_try.push_back(data_descriptor);
+
+        if (DataDescriptorsHaveCompatibleTypes(
+                use_inst->opcode(), use_in_operand_index,
+                descriptor_for_this_id, *data_descriptor)) {
+          synonyms_to_try.push_back(data_descriptor);
+        }
       }
       while (!synonyms_to_try.empty()) {
         auto synonym_to_try =
@@ -142,6 +148,15 @@
                                                : parent_block->terminator();
           }
 
+          if (GetTransformationContext()->GetFactManager()->BlockIsDead(
+                  GetIRContext()
+                      ->get_instr_block(instruction_to_insert_before)
+                      ->id())) {
+            // We cannot create a synonym via a composite extraction in a dead
+            // block, as the resulting id is irrelevant.
+            continue;
+          }
+
           assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
                      synonym_to_try->object()) &&
                  "Irrelevant ids can't participate in DataSynonym facts");
@@ -150,6 +165,11 @@
                                         instruction_to_insert_before),
               id_with_which_to_replace_use, synonym_to_try->object(),
               fuzzerutil::RepeatedFieldToVector(synonym_to_try->index())));
+          assert(GetTransformationContext()->GetFactManager()->IsSynonymous(
+                     MakeDataDescriptor(id_with_which_to_replace_use, {}),
+                     *synonym_to_try) &&
+                 "The extracted id must be synonymous with the component from "
+                 "which it was extracted.");
         }
 
         ApplyTransformation(TransformationReplaceIdWithSynonym(
@@ -162,5 +182,26 @@
   }
 }
 
+bool FuzzerPassApplyIdSynonyms::DataDescriptorsHaveCompatibleTypes(
+    SpvOp opcode, uint32_t use_in_operand_index,
+    const protobufs::DataDescriptor& dd1,
+    const protobufs::DataDescriptor& dd2) {
+  auto base_object_type_id_1 =
+      fuzzerutil::GetTypeId(GetIRContext(), dd1.object());
+  auto base_object_type_id_2 =
+      fuzzerutil::GetTypeId(GetIRContext(), dd2.object());
+  assert(base_object_type_id_1 && base_object_type_id_2 &&
+         "Data descriptors are invalid");
+
+  auto type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
+      GetIRContext(), base_object_type_id_1, dd1.index());
+  auto type_id_2 = fuzzerutil::WalkCompositeTypeIndices(
+      GetIRContext(), base_object_type_id_2, dd2.index());
+  assert(type_id_1 && type_id_2 && "Data descriptors have invalid types");
+
+  return TransformationReplaceIdWithSynonym::TypesAreCompatible(
+      GetIRContext(), opcode, use_in_operand_index, type_id_1, type_id_2);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.h b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
index 1a9213d..5deac10 100644
--- a/source/fuzz/fuzzer_pass_apply_id_synonyms.h
+++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.h
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_
-#define SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
-
 #include "source/opt/ir_context.h"
 
 namespace spvtools {
@@ -34,9 +33,19 @@
   ~FuzzerPassApplyIdSynonyms() override;
 
   void Apply() override;
+
+ private:
+  // Returns true if uses of |dd1| can be replaced with |dd2| and vice-versa
+  // with respect to the type. Concretely, returns true if |dd1| and |dd2| have
+  // the same type or both |dd1| and |dd2| are either a numerical or a vector
+  // type of integral components with possibly different signedness.
+  bool DataDescriptorsHaveCompatibleTypes(SpvOp opcode,
+                                          uint32_t use_in_operand_index,
+                                          const protobufs::DataDescriptor& dd1,
+                                          const protobufs::DataDescriptor& dd2);
 };
 
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_APPLY_ID_SYNONYMS_H_
diff --git a/source/fuzz/fuzzer_pass_construct_composites.cpp b/source/fuzz/fuzzer_pass_construct_composites.cpp
index 6443e89..584fa1a 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.cpp
+++ b/source/fuzz/fuzzer_pass_construct_composites.cpp
@@ -32,11 +32,14 @@
 FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
 
 void FuzzerPassConstructComposites::Apply() {
-  // Gather up the ids of all composite types.
+  // Gather up the ids of all composite types, but skip block-/buffer
+  // block-decorated struct types.
   std::vector<uint32_t> composite_type_ids;
   for (auto& inst : GetIRContext()->types_values()) {
     if (fuzzerutil::IsCompositeType(
-            GetIRContext()->get_type_mgr()->GetType(inst.result_id()))) {
+            GetIRContext()->get_type_mgr()->GetType(inst.result_id())) &&
+        !fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
+                                                     inst.result_id())) {
       composite_type_ids.push_back(inst.result_id());
     }
   }
diff --git a/source/fuzz/fuzzer_pass_construct_composites.h b/source/fuzz/fuzzer_pass_construct_composites.h
index 9853fad..c140bde 100644
--- a/source/fuzz/fuzzer_pass_construct_composites.h
+++ b/source/fuzz/fuzzer_pass_construct_composites.h
@@ -15,11 +15,11 @@
 #ifndef SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
 #define SOURCE_FUZZ_FUZZER_PASS_CONSTRUCT_COMPOSITES_H_
 
-#include "source/fuzz/fuzzer_pass.h"
-
 #include <map>
 #include <vector>
 
+#include "source/fuzz/fuzzer_pass.h"
+
 namespace spvtools {
 namespace fuzz {
 
diff --git a/source/fuzz/fuzzer_pass_copy_objects.cpp b/source/fuzz/fuzzer_pass_copy_objects.cpp
index 81326ac..9f7bbd6 100644
--- a/source/fuzz/fuzzer_pass_copy_objects.cpp
+++ b/source/fuzz/fuzzer_pass_copy_objects.cpp
@@ -41,6 +41,12 @@
                "The opcode of the instruction we might insert before must be "
                "the same as the opcode in the descriptor for the instruction");
 
+        if (GetTransformationContext()->GetFactManager()->BlockIsDead(
+                block->id())) {
+          // Don't create synonyms in dead blocks.
+          return;
+        }
+
         // Check whether it is legitimate to insert a copy before this
         // instruction.
         if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCopyObject,
@@ -54,13 +60,13 @@
           return;
         }
 
-        std::vector<opt::Instruction*> relevant_instructions =
-            FindAvailableInstructions(
-                function, block, inst_it,
-                [this](opt::IRContext* ir_context, opt::Instruction* inst) {
-                  return fuzzerutil::CanMakeSynonymOf(
-                      ir_context, *GetTransformationContext(), inst);
-                });
+        const auto relevant_instructions = FindAvailableInstructions(
+            function, block, inst_it,
+            [this](opt::IRContext* ir_context, opt::Instruction* inst) {
+              return TransformationAddSynonym::IsInstructionValid(
+                  ir_context, *GetTransformationContext(), inst,
+                  protobufs::TransformationAddSynonym::COPY_OBJECT);
+            });
 
         // At this point, |relevant_instructions| contains all the instructions
         // we might think of copying.
diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp
index aa0e243..4cd4804 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.cpp
+++ b/source/fuzz/fuzzer_pass_donate_modules.cpp
@@ -65,10 +65,11 @@
     std::unique_ptr<opt::IRContext> donor_ir_context = donor_suppliers_.at(
         GetFuzzerContext()->RandomIndex(donor_suppliers_))();
     assert(donor_ir_context != nullptr && "Supplying of donor failed");
-    assert(fuzzerutil::IsValid(
-               donor_ir_context.get(),
-               GetTransformationContext()->GetValidatorOptions()) &&
-           "The donor module must be valid");
+    assert(
+        fuzzerutil::IsValid(donor_ir_context.get(),
+                            GetTransformationContext()->GetValidatorOptions(),
+                            fuzzerutil::kSilentMessageConsumer) &&
+        "The donor module must be valid");
     // Donate the supplied module.
     //
     // Randomly decide whether to make the module livesafe (see
@@ -84,6 +85,16 @@
 
 void FuzzerPassDonateModules::DonateSingleModule(
     opt::IRContext* donor_ir_context, bool make_livesafe) {
+  // Check that the donated module has capabilities, supported by the recipient
+  // module.
+  for (const auto& capability_inst : donor_ir_context->capabilities()) {
+    auto capability =
+        static_cast<SpvCapability>(capability_inst.GetSingleWordInOperand(0));
+    if (!GetIRContext()->get_feature_mgr()->HasCapability(capability)) {
+      return;
+    }
+  }
+
   // The ids used by the donor module may very well clash with ids defined in
   // the recipient module.  Furthermore, some instructions defined in the donor
   // module will be equivalent to instructions defined in the recipient module,
@@ -588,7 +599,7 @@
   // Get the ids of functions in the donor module, topologically sorted
   // according to the donor's call graph.
   auto topological_order =
-      GetFunctionsInCallGraphTopologicalOrder(donor_ir_context);
+      CallGraph(donor_ir_context).GetFunctionsInTopologicalOrder();
 
   // Donate the functions in reverse topological order.  This ensures that a
   // function gets donated before any function that depends on it.  This allows
@@ -646,12 +657,13 @@
           }
         });
 
-    if (make_livesafe) {
-      // Make the function livesafe and then add it.
-      AddLivesafeFunction(*function_to_donate, donor_ir_context,
-                          *original_id_to_donated_id, donated_instructions);
-    } else {
-      // Add the function in a non-livesafe manner.
+    // If |make_livesafe| is true, try to add the function in a livesafe manner.
+    // Otherwise (if |make_lifesafe| is false or an attempt to make the function
+    // livesafe has failed), add the function in a non-livesafe manner.
+    if (!make_livesafe ||
+        !MaybeAddLivesafeFunction(*function_to_donate, donor_ir_context,
+                                  *original_id_to_donated_id,
+                                  donated_instructions)) {
       ApplyTransformation(TransformationAddFunction(donated_instructions));
     }
   }
@@ -773,6 +785,7 @@
     const opt::Instruction& instruction) const {
   switch (instruction.opcode()) {
     case SpvOpTypeArray:
+    case SpvOpTypeBool:
     case SpvOpTypeFloat:
     case SpvOpTypeInt:
     case SpvOpTypeMatrix:
@@ -784,52 +797,6 @@
   }
 }
 
-std::vector<uint32_t>
-FuzzerPassDonateModules::GetFunctionsInCallGraphTopologicalOrder(
-    opt::IRContext* context) {
-  CallGraph call_graph(context);
-
-  // This is an implementation of Kahn’s algorithm for topological sorting.
-
-  // This is the sorted order of function ids that we will eventually return.
-  std::vector<uint32_t> result;
-
-  // Get a copy of the initial in-degrees of all functions.  The algorithm
-  // involves decrementing these values, hence why we work on a copy.
-  std::map<uint32_t, uint32_t> function_in_degree =
-      call_graph.GetFunctionInDegree();
-
-  // Populate a queue with all those function ids with in-degree zero.
-  std::queue<uint32_t> queue;
-  for (auto& entry : function_in_degree) {
-    if (entry.second == 0) {
-      queue.push(entry.first);
-    }
-  }
-
-  // Pop ids from the queue, adding them to the sorted order and decreasing the
-  // in-degrees of their successors.  A successor who's in-degree becomes zero
-  // gets added to the queue.
-  while (!queue.empty()) {
-    auto next = queue.front();
-    queue.pop();
-    result.push_back(next);
-    for (auto successor : call_graph.GetDirectCallees(next)) {
-      assert(function_in_degree.at(successor) > 0 &&
-             "The in-degree cannot be zero if the function is a successor.");
-      function_in_degree[successor] = function_in_degree.at(successor) - 1;
-      if (function_in_degree.at(successor) == 0) {
-        queue.push(successor);
-      }
-    }
-  }
-
-  assert(result.size() == function_in_degree.size() &&
-         "Every function should appear in the sort.");
-
-  return result;
-}
-
 void FuzzerPassDonateModules::HandleOpArrayLength(
     const opt::Instruction& instruction,
     std::map<uint32_t, uint32_t>* original_id_to_donated_id,
@@ -1007,7 +974,96 @@
       input_operands));
 }
 
-void FuzzerPassDonateModules::AddLivesafeFunction(
+bool FuzzerPassDonateModules::CreateLoopLimiterInfo(
+    opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
+    const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
+    protobufs::LoopLimiterInfo* out) {
+  assert(loop_header.IsLoopHeader() && "|loop_header| is not a loop header");
+
+  // Grab the loop header's id, mapped to its donated value.
+  out->set_loop_header_id(original_id_to_donated_id.at(loop_header.id()));
+
+  // Get fresh ids that will be used to load the loop limiter, increment
+  // it, compare it with the loop limit, and an id for a new block that
+  // will contain the loop's original terminator.
+  out->set_load_id(GetFuzzerContext()->GetFreshId());
+  out->set_increment_id(GetFuzzerContext()->GetFreshId());
+  out->set_compare_id(GetFuzzerContext()->GetFreshId());
+  out->set_logical_op_id(GetFuzzerContext()->GetFreshId());
+
+  // We are creating a branch from the back-edge block to the merge block. Thus,
+  // if merge block has any OpPhi instructions, we might need to adjust
+  // them.
+
+  // Note that the loop might have an unreachable back-edge block. This means
+  // that the loop can't iterate, so we don't need to adjust anything.
+  const auto back_edge_block_id = TransformationAddFunction::GetBackEdgeBlockId(
+      donor_ir_context, loop_header.id());
+  if (!back_edge_block_id) {
+    return true;
+  }
+
+  auto* back_edge_block = donor_ir_context->cfg()->block(back_edge_block_id);
+  assert(back_edge_block && "|back_edge_block_id| is invalid");
+
+  const auto* merge_block =
+      donor_ir_context->cfg()->block(loop_header.MergeBlockId());
+  assert(merge_block && "Loop header has invalid merge block id");
+
+  // We don't need to adjust anything if there is already a branch from
+  // the back-edge block to the merge block.
+  if (back_edge_block->IsSuccessor(merge_block)) {
+    return true;
+  }
+
+  // Adjust OpPhi instructions in the |merge_block|.
+  for (const auto& inst : *merge_block) {
+    if (inst.opcode() != SpvOpPhi) {
+      break;
+    }
+
+    // There is no simple way to ensure that a chosen operand for the OpPhi
+    // instruction will never cause any problems (e.g. if we choose an
+    // integer id, it might have a zero value when we branch from the back
+    // edge block. This might cause a division by 0 later in the function.).
+    // Thus, we ignore possible problems and proceed as follows:
+    // - if any of the existing OpPhi operands dominates the back-edge
+    //   block - use it
+    // - if OpPhi has a basic type (see IsBasicType method) - create
+    //   a zero constant
+    // - otherwise, we can't add a livesafe function.
+    uint32_t suitable_operand_id = 0;
+    for (uint32_t i = 0; i < inst.NumInOperands(); i += 2) {
+      auto dependency_inst_id = inst.GetSingleWordInOperand(i);
+
+      if (fuzzerutil::IdIsAvailableBeforeInstruction(
+              donor_ir_context, back_edge_block->terminator(),
+              dependency_inst_id)) {
+        suitable_operand_id = original_id_to_donated_id.at(dependency_inst_id);
+        break;
+      }
+    }
+
+    if (suitable_operand_id == 0 &&
+        IsBasicType(
+            *donor_ir_context->get_def_use_mgr()->GetDef(inst.type_id()))) {
+      // We mark this constant as irrelevant so that we can replace it
+      // with more interesting value later.
+      suitable_operand_id = FindOrCreateZeroConstant(
+          original_id_to_donated_id.at(inst.type_id()), true);
+    }
+
+    if (suitable_operand_id == 0) {
+      return false;
+    }
+
+    out->add_phi_id(suitable_operand_id);
+  }
+
+  return true;
+}
+
+bool FuzzerPassDonateModules::MaybeAddLivesafeFunction(
     const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
     const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
     const std::vector<protobufs::Instruction>& donated_instructions) {
@@ -1035,16 +1091,13 @@
   for (auto& block : function_to_donate) {
     if (block.IsLoopHeader()) {
       protobufs::LoopLimiterInfo loop_limiter;
-      // Grab the loop header's id, mapped to its donated value.
-      loop_limiter.set_loop_header_id(original_id_to_donated_id.at(block.id()));
-      // Get fresh ids that will be used to load the loop limiter, increment
-      // it, compare it with the loop limit, and an id for a new block that
-      // will contain the loop's original terminator.
-      loop_limiter.set_load_id(GetFuzzerContext()->GetFreshId());
-      loop_limiter.set_increment_id(GetFuzzerContext()->GetFreshId());
-      loop_limiter.set_compare_id(GetFuzzerContext()->GetFreshId());
-      loop_limiter.set_logical_op_id(GetFuzzerContext()->GetFreshId());
-      loop_limiters.emplace_back(loop_limiter);
+
+      if (!CreateLoopLimiterInfo(donor_ir_context, block,
+                                 original_id_to_donated_id, &loop_limiter)) {
+        return false;
+      }
+
+      loop_limiters.emplace_back(std::move(loop_limiter));
     }
   }
 
@@ -1136,27 +1189,24 @@
     }
   }
 
-  // If the function contains OpKill or OpUnreachable instructions, and has
-  // non-void return type, then we need a value %v to use in order to turn
-  // these into instructions of the form OpReturn %v.
-  uint32_t kill_unreachable_return_value_id;
+  // If |function_to_donate| has non-void return type and contains an
+  // OpKill/OpUnreachable instruction, then a value is needed in order to turn
+  // these into instructions of the form OpReturnValue %value_id.
+  uint32_t kill_unreachable_return_value_id = 0;
   auto function_return_type_inst =
       donor_ir_context->get_def_use_mgr()->GetDef(function_to_donate.type_id());
-  if (function_return_type_inst->opcode() == SpvOpTypeVoid) {
-    // The return type is void, so we don't need a return value.
-    kill_unreachable_return_value_id = 0;
-  } else {
-    // We do need a return value; we use zero.
-    assert(function_return_type_inst->opcode() != SpvOpTypePointer &&
-           "Function return type must not be a pointer.");
+  if (function_return_type_inst->opcode() != SpvOpTypeVoid &&
+      fuzzerutil::FunctionContainsOpKillOrUnreachable(function_to_donate)) {
     kill_unreachable_return_value_id = FindOrCreateZeroConstant(
         original_id_to_donated_id.at(function_return_type_inst->result_id()),
         false);
   }
+
   // Add the function in a livesafe manner.
   ApplyTransformation(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 c59ad71..0424cec 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.h
+++ b/source/fuzz/fuzzer_pass_donate_modules.h
@@ -128,14 +128,24 @@
       std::map<uint32_t, uint32_t>* original_id_to_donated_id,
       std::vector<protobufs::Instruction>* donated_instructions);
 
+  // Tries to create a protobufs::LoopLimiterInfo given a loop header basic
+  // block. Returns true if successful and outputs loop limiter into the |out|
+  // variable. Otherwise, returns false. |out| contains an undefined value when
+  // this function returns false.
+  bool CreateLoopLimiterInfo(
+      opt::IRContext* donor_ir_context, const opt::BasicBlock& loop_header,
+      const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
+      protobufs::LoopLimiterInfo* out);
+
   // Requires that |donated_instructions| represents a prepared version of the
   // instructions of |function_to_donate| (which comes from |donor_ir_context|)
   // ready for donation, and |original_id_to_donated_id| maps ids from
   // |donor_ir_context| to their corresponding ids in the recipient module.
   //
-  // Adds a livesafe version of the function, based on |donated_instructions|,
-  // to the recipient module.
-  void AddLivesafeFunction(
+  // Attempts to add a livesafe version of the function, based on
+  // |donated_instructions|, to the recipient module. Returns true if the
+  // donation was successful, false otherwise.
+  bool MaybeAddLivesafeFunction(
       const opt::Function& function_to_donate, opt::IRContext* donor_ir_context,
       const std::map<uint32_t, uint32_t>& original_id_to_donated_id,
       const std::vector<protobufs::Instruction>& donated_instructions);
@@ -144,12 +154,6 @@
   // array or struct; i.e. it is not an opaque type.
   bool IsBasicType(const opt::Instruction& instruction) const;
 
-  // Returns the ids of all functions in |context| in a topological order in
-  // relation to the call graph of |context|, which is assumed to be recursion-
-  // free.
-  static std::vector<uint32_t> GetFunctionsInCallGraphTopologicalOrder(
-      opt::IRContext* context);
-
   // Functions that supply SPIR-V modules
   std::vector<fuzzerutil::ModuleSupplier> donor_suppliers_;
 };
diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
new file mode 100644
index 0000000..1651a9d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_duplicate_regions_with_selections.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_duplicate_region_with_selection.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassDuplicateRegionsWithSelections::
+    FuzzerPassDuplicateRegionsWithSelections(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassDuplicateRegionsWithSelections::
+    ~FuzzerPassDuplicateRegionsWithSelections() = default;
+
+void FuzzerPassDuplicateRegionsWithSelections::Apply() {
+  // Iterate over all of the functions in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    // Randomly decide whether to apply the transformation.
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfDuplicatingRegionWithSelection())) {
+      continue;
+    }
+    std::vector<opt::BasicBlock*> candidate_entry_blocks;
+    for (auto& block : function) {
+      // We don't consider the first block to be the entry block, since it
+      // could contain OpVariable instructions that would require additional
+      // operations to be reassigned.
+      // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3778):
+      //     Consider extending this fuzzer pass to allow the first block to be
+      //     used in duplication.
+      if (&block == &*function.begin()) {
+        continue;
+      }
+      candidate_entry_blocks.push_back(&block);
+    }
+    if (candidate_entry_blocks.empty()) {
+      continue;
+    }
+    // Randomly choose the entry block.
+    auto entry_block = candidate_entry_blocks[GetFuzzerContext()->RandomIndex(
+        candidate_entry_blocks)];
+    auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(&function);
+    auto postdominator_analysis =
+        GetIRContext()->GetPostDominatorAnalysis(&function);
+    std::vector<opt::BasicBlock*> candidate_exit_blocks;
+    for (auto postdominates_entry_block = entry_block;
+         postdominates_entry_block != nullptr;
+         postdominates_entry_block = postdominator_analysis->ImmediateDominator(
+             postdominates_entry_block)) {
+      // The candidate exit block must be dominated by the entry block and the
+      // entry block must be post-dominated by the candidate exit block. Ignore
+      // the block if it heads a selection construct or a loop construct.
+      if (dominator_analysis->Dominates(entry_block,
+                                        postdominates_entry_block) &&
+          !postdominates_entry_block->GetMergeInst()) {
+        candidate_exit_blocks.push_back(postdominates_entry_block);
+      }
+    }
+    if (candidate_exit_blocks.empty()) {
+      continue;
+    }
+    // Randomly choose the exit block.
+    auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex(
+        candidate_exit_blocks)];
+
+    auto region_blocks =
+        TransformationDuplicateRegionWithSelection::GetRegionBlocks(
+            GetIRContext(), entry_block, exit_block);
+
+    // Construct |original_label_to_duplicate_label| by iterating over all
+    // blocks in the region. Construct |original_id_to_duplicate_id| and
+    // |original_id_to_phi_id| by iterating over all instructions in each block.
+    std::map<uint32_t, uint32_t> original_label_to_duplicate_label;
+    std::map<uint32_t, uint32_t> original_id_to_duplicate_id;
+    std::map<uint32_t, uint32_t> original_id_to_phi_id;
+    for (auto& block : region_blocks) {
+      original_label_to_duplicate_label[block->id()] =
+          GetFuzzerContext()->GetFreshId();
+      for (auto& instr : *block) {
+        if (instr.result_id()) {
+          original_id_to_duplicate_id[instr.result_id()] =
+              GetFuzzerContext()->GetFreshId();
+          auto final_instruction = &*exit_block->tail();
+          // &*exit_block->tail() is the final instruction of the region.
+          // The instruction is available at the end of the region if and only
+          // if it is available before this final instruction or it is the final
+          // instruction.
+          if ((&instr == final_instruction ||
+               fuzzerutil::IdIsAvailableBeforeInstruction(
+                   GetIRContext(), final_instruction, instr.result_id()))) {
+            original_id_to_phi_id[instr.result_id()] =
+                GetFuzzerContext()->GetFreshId();
+          }
+        }
+      }
+    }
+    // Randomly decide between value "true" or "false" for a bool constant.
+    // Make sure the transformation has access to a bool constant to be used
+    // while creating conditional construct.
+    auto condition_id =
+        FindOrCreateBoolConstant(GetFuzzerContext()->ChooseEven(), true);
+
+    TransformationDuplicateRegionWithSelection transformation =
+        TransformationDuplicateRegionWithSelection(
+            GetFuzzerContext()->GetFreshId(), condition_id,
+            GetFuzzerContext()->GetFreshId(), entry_block->id(),
+            exit_block->id(), original_label_to_duplicate_label,
+            original_id_to_duplicate_id, original_id_to_phi_id);
+    MaybeApplyTransformation(transformation);
+  }
+}
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
new file mode 100644
index 0000000..3fae698
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_DUPLICATE_REGIONS_WITH_SELECTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that iterates over the whole module. For each function it
+// finds a single-entry, single-exit region with its constructs and their merge
+// blocks either completely within or completely outside the region. It
+// duplicates this region using the corresponding transformation.
+class FuzzerPassDuplicateRegionsWithSelections : public FuzzerPass {
+ public:
+  FuzzerPassDuplicateRegionsWithSelections(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassDuplicateRegionsWithSelections() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_
diff --git a/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp b/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp
new file mode 100644
index 0000000..1416fe0
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp
@@ -0,0 +1,67 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_expand_vector_reductions.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_expand_vector_reduction.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassExpandVectorReductions::FuzzerPassExpandVectorReductions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassExpandVectorReductions::~FuzzerPassExpandVectorReductions() = default;
+
+void FuzzerPassExpandVectorReductions::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        // Randomly decides whether the transformation will be applied.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfExpandingVectorReduction())) {
+          continue;
+        }
+
+        // |instruction| must be OpAny or OpAll.
+        if (instruction.opcode() != SpvOpAny &&
+            instruction.opcode() != SpvOpAll) {
+          continue;
+        }
+
+        // It must be able to make a synonym of |instruction|.
+        if (!fuzzerutil::CanMakeSynonymOf(
+                GetIRContext(), *GetTransformationContext(), &instruction)) {
+          continue;
+        }
+
+        // Applies the expand vector reduction transformation.
+        ApplyTransformation(TransformationExpandVectorReduction(
+            instruction.result_id(),
+            GetFuzzerContext()->GetFreshIds(
+                TransformationExpandVectorReduction::GetRequiredFreshIdCount(
+                    GetIRContext(), &instruction))));
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_expand_vector_reductions.h b/source/fuzz/fuzzer_pass_expand_vector_reductions.h
new file mode 100644
index 0000000..ae3238b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_expand_vector_reductions.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_EXPAND_VECTOR_REDUCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_EXPAND_VECTOR_REDUCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This fuzzer pass adds synonyms for the OpAny and OpAll instructions. It
+// iterates over the module, checks if there are any OpAny or OpAll applicable
+// instructions and randomly applies the expand vector reduction transformation.
+class FuzzerPassExpandVectorReductions : public FuzzerPass {
+ public:
+  FuzzerPassExpandVectorReductions(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassExpandVectorReductions();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_EXPAND_VECTOR_REDUCTIONS_H_
diff --git a/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp b/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
new file mode 100644
index 0000000..c7c2933
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
@@ -0,0 +1,250 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_flatten_conditional_branches.h"
+
+#include "source/fuzz/comparator_deep_blocks_first.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_flatten_conditional_branch.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that randomly selects conditional branches to flatten and
+// flattens them, if possible.
+FuzzerPassFlattenConditionalBranches::FuzzerPassFlattenConditionalBranches(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassFlattenConditionalBranches::~FuzzerPassFlattenConditionalBranches() =
+    default;
+
+void FuzzerPassFlattenConditionalBranches::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    // Get all the selection headers that we want to flatten. We need to collect
+    // all of them first, because, since we are changing the structure of the
+    // module, it's not safe to modify them while iterating.
+    std::vector<opt::BasicBlock*> selection_headers;
+
+    for (auto& block : function) {
+      // Randomly decide whether to consider this block.
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfFlatteningConditionalBranch())) {
+        continue;
+      }
+
+      // Only consider this block if it is the header of a conditional, with a
+      // non-irrelevant condition.
+      if (block.GetMergeInst() &&
+          block.GetMergeInst()->opcode() == SpvOpSelectionMerge &&
+          block.terminator()->opcode() == SpvOpBranchConditional &&
+          !GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+              block.terminator()->GetSingleWordInOperand(0))) {
+        selection_headers.emplace_back(&block);
+      }
+    }
+
+    // Sort the headers so that those that are more deeply nested are considered
+    // first, possibly enabling outer conditionals to be flattened.
+    std::sort(selection_headers.begin(), selection_headers.end(),
+              ComparatorDeepBlocksFirst(GetIRContext()));
+
+    // Apply the transformation to the headers which can be flattened.
+    for (auto header : selection_headers) {
+      // Make a set to keep track of the instructions that need fresh ids.
+      std::set<opt::Instruction*> instructions_that_need_ids;
+
+      // Do not consider this header if the conditional cannot be flattened.
+      if (!TransformationFlattenConditionalBranch::
+              GetProblematicInstructionsIfConditionalCanBeFlattened(
+                  GetIRContext(), header, &instructions_that_need_ids)) {
+        continue;
+      }
+
+      uint32_t convergence_block_id =
+          TransformationFlattenConditionalBranch::FindConvergenceBlock(
+              GetIRContext(), *header);
+
+      // If the SPIR-V version is restricted so that OpSelect can only work on
+      // scalar, pointer and vector types then we cannot apply this
+      // transformation to a header whose convergence block features OpPhi
+      // instructions on different types, as we cannot convert such instructions
+      // to OpSelect instructions.
+      if (TransformationFlattenConditionalBranch::
+              OpSelectArgumentsAreRestricted(GetIRContext())) {
+        if (!GetIRContext()
+                 ->cfg()
+                 ->block(convergence_block_id)
+                 ->WhileEachPhiInst(
+                     [this](opt::Instruction* phi_instruction) -> bool {
+                       switch (GetIRContext()
+                                   ->get_def_use_mgr()
+                                   ->GetDef(phi_instruction->type_id())
+                                   ->opcode()) {
+                         case SpvOpTypeBool:
+                         case SpvOpTypeInt:
+                         case SpvOpTypeFloat:
+                         case SpvOpTypePointer:
+                         case SpvOpTypeVector:
+                           return true;
+                         default:
+                           return false;
+                       }
+                     })) {
+          // An OpPhi is performed on a type not supported by OpSelect; we
+          // cannot flatten this selection.
+          continue;
+        }
+      }
+
+      // If the construct's convergence block features OpPhi instructions with
+      // vector result types then we may be *forced*, by the SPIR-V version, to
+      // turn these into component-wise OpSelect instructions, or we might wish
+      // to do so anyway.  The following booleans capture whether we will opt
+      // to use a component-wise select even if we don't have to.
+      bool use_component_wise_2d_select_even_if_optional =
+          GetFuzzerContext()->ChooseEven();
+      bool use_component_wise_3d_select_even_if_optional =
+          GetFuzzerContext()->ChooseEven();
+      bool use_component_wise_4d_select_even_if_optional =
+          GetFuzzerContext()->ChooseEven();
+
+      // If we do need to perform any component-wise selections, we will need a
+      // fresh id for a boolean vector representing the selection's condition
+      // repeated N times, where N is the vector dimension.
+      uint32_t fresh_id_for_bvec2_selector = 0;
+      uint32_t fresh_id_for_bvec3_selector = 0;
+      uint32_t fresh_id_for_bvec4_selector = 0;
+
+      GetIRContext()
+          ->cfg()
+          ->block(convergence_block_id)
+          ->ForEachPhiInst([this, &fresh_id_for_bvec2_selector,
+                            &fresh_id_for_bvec3_selector,
+                            &fresh_id_for_bvec4_selector,
+                            use_component_wise_2d_select_even_if_optional,
+                            use_component_wise_3d_select_even_if_optional,
+                            use_component_wise_4d_select_even_if_optional](
+                               opt::Instruction* phi_instruction) {
+            opt::Instruction* type_instruction =
+                GetIRContext()->get_def_use_mgr()->GetDef(
+                    phi_instruction->type_id());
+            switch (type_instruction->opcode()) {
+              case SpvOpTypeVector: {
+                uint32_t dimension =
+                    type_instruction->GetSingleWordInOperand(1);
+                switch (dimension) {
+                  case 2:
+                    PrepareForOpPhiOnVectors(
+                        dimension,
+                        use_component_wise_2d_select_even_if_optional,
+                        &fresh_id_for_bvec2_selector);
+                    break;
+                  case 3:
+                    PrepareForOpPhiOnVectors(
+                        dimension,
+                        use_component_wise_3d_select_even_if_optional,
+                        &fresh_id_for_bvec3_selector);
+                    break;
+                  case 4:
+                    PrepareForOpPhiOnVectors(
+                        dimension,
+                        use_component_wise_4d_select_even_if_optional,
+                        &fresh_id_for_bvec4_selector);
+                    break;
+                  default:
+                    assert(false && "Invalid vector dimension.");
+                }
+                break;
+              }
+              default:
+                break;
+            }
+          });
+
+      // Some instructions will require to be enclosed inside conditionals
+      // because they have side effects (for example, loads and stores). Some of
+      // this have no result id, so we require instruction descriptors to
+      // identify them. Each of them is associated with the necessary ids for it
+      // via a SideEffectWrapperInfo message.
+      std::vector<protobufs::SideEffectWrapperInfo> wrappers_info;
+
+      for (auto instruction : instructions_that_need_ids) {
+        protobufs::SideEffectWrapperInfo wrapper_info;
+        *wrapper_info.mutable_instruction() =
+            MakeInstructionDescriptor(GetIRContext(), instruction);
+        wrapper_info.set_merge_block_id(GetFuzzerContext()->GetFreshId());
+        wrapper_info.set_execute_block_id(GetFuzzerContext()->GetFreshId());
+
+        // If the instruction has a non-void result id, we need to define more
+        // fresh ids and provide an id of the suitable type whose value can be
+        // copied in order to create a placeholder id.
+        if (TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder(
+                GetIRContext(), *instruction)) {
+          wrapper_info.set_actual_result_id(GetFuzzerContext()->GetFreshId());
+          wrapper_info.set_alternative_block_id(
+              GetFuzzerContext()->GetFreshId());
+          wrapper_info.set_placeholder_result_id(
+              GetFuzzerContext()->GetFreshId());
+
+          // The id will be a zero constant if the type allows it, and an
+          // OpUndef otherwise. We want to avoid using OpUndef, if possible, to
+          // avoid undefined behaviour in the module as much as possible.
+          if (fuzzerutil::CanCreateConstant(GetIRContext(),
+                                            instruction->type_id())) {
+            wrapper_info.set_value_to_copy_id(
+                FindOrCreateZeroConstant(instruction->type_id(), true));
+          } else {
+            wrapper_info.set_value_to_copy_id(
+                FindOrCreateGlobalUndef(instruction->type_id()));
+          }
+        }
+
+        wrappers_info.emplace_back(wrapper_info);
+      }
+
+      // Apply the transformation, evenly choosing whether to lay out the true
+      // branch or the false branch first.
+      ApplyTransformation(TransformationFlattenConditionalBranch(
+          header->id(), GetFuzzerContext()->ChooseEven(),
+          fresh_id_for_bvec2_selector, fresh_id_for_bvec3_selector,
+          fresh_id_for_bvec4_selector, wrappers_info));
+    }
+  }
+}
+
+void FuzzerPassFlattenConditionalBranches::PrepareForOpPhiOnVectors(
+    uint32_t vector_dimension, bool use_vector_select_if_optional,
+    uint32_t* fresh_id_for_bvec_selector) {
+  if (*fresh_id_for_bvec_selector != 0) {
+    // We already have a fresh id for a component-wise OpSelect of this
+    // dimension
+    return;
+  }
+  if (TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
+          GetIRContext()) ||
+      use_vector_select_if_optional) {
+    // We either have to, or have chosen to, perform a component-wise select, so
+    // we ensure that the right boolean vector type is available, and grab a
+    // fresh id.
+    FindOrCreateVectorType(FindOrCreateBoolType(), vector_dimension);
+    *fresh_id_for_bvec_selector = GetFuzzerContext()->GetFreshId();
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_flatten_conditional_branches.h b/source/fuzz/fuzzer_pass_flatten_conditional_branches.h
new file mode 100644
index 0000000..76f7782
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_flatten_conditional_branches.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_FLATTEN_CONDITIONAL_BRANCHES_H
+#define SOURCE_FUZZ_FUZZER_PASS_FLATTEN_CONDITIONAL_BRANCHES_H
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class FuzzerPassFlattenConditionalBranches : public FuzzerPass {
+ public:
+  FuzzerPassFlattenConditionalBranches(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassFlattenConditionalBranches() override;
+
+  void Apply() override;
+
+ private:
+  // If the SPIR-V version requires vector OpSelects to be component-wise, or
+  // if |use_vector_select_if_optional| holds, |fresh_id_for_bvec_selector| is
+  // populated with a fresh id if it is currently zero, and a
+  // |vector_dimension|-dimensional boolean vector type is added to the module
+  // if not already present.
+  void PrepareForOpPhiOnVectors(uint32_t vector_dimension,
+                                bool use_vector_select_if_optional,
+                                uint32_t* fresh_id_for_bvec_selector);
+};
+}  // namespace fuzz
+}  // namespace spvtools
+#endif  // SOURCE_FUZZ_FUZZER_PASS_FLATTEN_CONDITIONAL_BRANCHES_H
diff --git a/source/fuzz/fuzzer_pass_inline_functions.cpp b/source/fuzz/fuzzer_pass_inline_functions.cpp
new file mode 100644
index 0000000..90160d8
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_inline_functions.cpp
@@ -0,0 +1,104 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_inline_functions.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_inline_function.h"
+#include "source/fuzz/transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassInlineFunctions::FuzzerPassInlineFunctions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassInlineFunctions::~FuzzerPassInlineFunctions() = default;
+
+void FuzzerPassInlineFunctions::Apply() {
+  // |function_call_instructions| are the instructions that will be inlined.
+  // First, they will be collected and then do the inlining in another loop.
+  // This avoids changing the module while it is being inspected.
+  std::vector<opt::Instruction*> function_call_instructions;
+
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfInliningFunction())) {
+          continue;
+        }
+
+        // |instruction| must be suitable for inlining.
+        if (!TransformationInlineFunction::IsSuitableForInlining(
+                GetIRContext(), &instruction)) {
+          continue;
+        }
+
+        function_call_instructions.push_back(&instruction);
+      }
+    }
+  }
+
+  // Once the function calls have been collected, it's time to actually create
+  // and apply the inlining transformations.
+  for (auto& function_call_instruction : function_call_instructions) {
+    // If |function_call_instruction| is not the penultimate instruction in its
+    // block or its block termination instruction is not OpBranch, then try to
+    // split |function_call_block| such that the conditions are met.
+    auto* function_call_block =
+        GetIRContext()->get_instr_block(function_call_instruction);
+    if ((function_call_instruction != &*--function_call_block->tail() ||
+         function_call_block->terminator()->opcode() != SpvOpBranch) &&
+        !MaybeApplyTransformation(TransformationSplitBlock(
+            MakeInstructionDescriptor(GetIRContext(),
+                                      function_call_instruction->NextNode()),
+            GetFuzzerContext()->GetFreshId()))) {
+      continue;
+    }
+
+    auto* called_function = fuzzerutil::FindFunction(
+        GetIRContext(), function_call_instruction->GetSingleWordInOperand(0));
+
+    // Mapping the called function instructions.
+    std::map<uint32_t, uint32_t> result_id_map;
+    for (auto& called_function_block : *called_function) {
+      // The called function entry block label will not be inlined.
+      if (&called_function_block != &*called_function->entry()) {
+        result_id_map[called_function_block.GetLabelInst()->result_id()] =
+            GetFuzzerContext()->GetFreshId();
+      }
+
+      for (auto& instruction_to_inline : called_function_block) {
+        // The instructions are mapped to fresh ids.
+        if (instruction_to_inline.HasResultId()) {
+          result_id_map[instruction_to_inline.result_id()] =
+              GetFuzzerContext()->GetFreshId();
+        }
+      }
+    }
+
+    // Applies the inline function transformation.
+    ApplyTransformation(TransformationInlineFunction(
+        function_call_instruction->result_id(), result_id_map));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_inline_functions.h b/source/fuzz/fuzzer_pass_inline_functions.h
new file mode 100644
index 0000000..37295d1
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_inline_functions.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_INLINE_FUNCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Looks for OpFunctionCall instructions and randomly decides which ones to
+// inline. If the instructions of the called function are going to be inlined,
+// then a mapping, between their result ids and suitable ids, is done.
+class FuzzerPassInlineFunctions : public FuzzerPass {
+ public:
+  FuzzerPassInlineFunctions(opt::IRContext* ir_context,
+                            TransformationContext* transformation_context,
+                            FuzzerContext* fuzzer_context,
+                            protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassInlineFunctions() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_INLINE_FUNCTIONS_H_
diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp
new file mode 100644
index 0000000..0e40b49
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp
@@ -0,0 +1,156 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 "fuzzer_pass_interchange_signedness_of_integer_operands.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/transformation_record_synonymous_constants.h"
+#include "source/fuzz/transformation_replace_id_with_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassInterchangeSignednessOfIntegerOperands::
+    FuzzerPassInterchangeSignednessOfIntegerOperands(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassInterchangeSignednessOfIntegerOperands::
+    ~FuzzerPassInterchangeSignednessOfIntegerOperands() = default;
+
+void FuzzerPassInterchangeSignednessOfIntegerOperands::Apply() {
+  // 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
+  // be used to replace it.
+  std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>> uses_to_replace;
+
+  for (auto constant : GetIRContext()->GetConstants()) {
+    uint32_t constant_id = constant->result_id();
+
+    // We want to record the synonymity of an integer constant with another
+    // constant with opposite signedness, and this can only be done if they are
+    // not irrelevant.
+    if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+            constant_id)) {
+      continue;
+    }
+
+    uint32_t toggled_id =
+        FindOrCreateToggledIntegerConstant(constant->result_id());
+    if (!toggled_id) {
+      // Not an integer constant
+      continue;
+    }
+
+    assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+               toggled_id) &&
+           "FindOrCreateToggledConstant can't produce an irrelevant id");
+
+    // Record synonymous constants
+    ApplyTransformation(
+        TransformationRecordSynonymousConstants(constant_id, toggled_id));
+
+    // Find all the uses of the constant and, for each, probabilistically
+    // decide whether to replace it.
+    GetIRContext()->get_def_use_mgr()->ForEachUse(
+        constant_id,
+        [this, toggled_id, &uses_to_replace](opt::Instruction* use_inst,
+                                             uint32_t use_index) -> void {
+          if (GetFuzzerContext()->ChoosePercentage(
+                  GetFuzzerContext()
+                      ->GetChanceOfInterchangingSignednessOfIntegerOperands())) {
+            MaybeAddUseToReplace(use_inst, use_index, toggled_id,
+                                 &uses_to_replace);
+          }
+        });
+  }
+
+  // Replace the ids if it is allowed.
+  for (auto use_to_replace : uses_to_replace) {
+    MaybeApplyTransformation(TransformationReplaceIdWithSynonym(
+        use_to_replace.first, use_to_replace.second));
+  }
+}
+
+uint32_t FuzzerPassInterchangeSignednessOfIntegerOperands::
+    FindOrCreateToggledIntegerConstant(uint32_t id) {
+  // |id| must not be a specialization constant because we do not know the value
+  // of specialization constants.
+  if (opt::IsSpecConstantInst(
+          GetIRContext()->get_def_use_mgr()->GetDef(id)->opcode())) {
+    return 0;
+  }
+
+  auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(id);
+
+  // This pass only toggles integer constants.
+  if (!constant->AsIntConstant() &&
+      (!constant->AsVectorConstant() ||
+       !constant->AsVectorConstant()->component_type()->AsInteger())) {
+    return 0;
+  }
+
+  if (auto integer = constant->AsIntConstant()) {
+    auto type = integer->type()->AsInteger();
+
+    // Find or create and return the toggled constant.
+    return FindOrCreateIntegerConstant(std::vector<uint32_t>(integer->words()),
+                                       type->width(), !type->IsSigned(), false);
+  }
+
+  // The constant is an integer vector.
+
+  // Find the component type.
+  auto component_type =
+      constant->AsVectorConstant()->component_type()->AsInteger();
+
+  // Find or create the toggled component type.
+  uint32_t toggled_component_type = FindOrCreateIntegerType(
+      component_type->width(), !component_type->IsSigned());
+
+  // Get the information about the toggled components. We need to extract this
+  // information now because the analyses might be invalidated, which would make
+  // the constant and component_type variables invalid.
+  std::vector<std::vector<uint32_t>> component_words;
+
+  for (auto component : constant->AsVectorConstant()->GetComponents()) {
+    component_words.push_back(component->AsIntConstant()->words());
+  }
+  uint32_t width = component_type->width();
+  bool is_signed = !component_type->IsSigned();
+
+  std::vector<uint32_t> toggled_components;
+
+  // Find or create the toggled components.
+  for (auto words : component_words) {
+    toggled_components.push_back(
+        FindOrCreateIntegerConstant(words, width, is_signed, false));
+  }
+
+  // Find or create the required toggled vector type.
+  uint32_t toggled_type = FindOrCreateVectorType(
+      toggled_component_type, (uint32_t)toggled_components.size());
+
+  // Find or create and return the toggled vector constant.
+  return FindOrCreateCompositeConstant(toggled_components, toggled_type, false);
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h
new file mode 100644
index 0000000..06882f4
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A pass that:
+// - Finds all the integer constant (scalar and vector) definitions in the
+//   module and adds the definitions of the integer with the same data words but
+//   opposite signedness. If the synonym is already in the module, it does not
+//   add a new one.
+// - For each use of an integer constant where its signedness does not matter,
+// decides whether to change it to the id of the toggled constant.
+class FuzzerPassInterchangeSignednessOfIntegerOperands : public FuzzerPass {
+ public:
+  FuzzerPassInterchangeSignednessOfIntegerOperands(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassInterchangeSignednessOfIntegerOperands() override;
+
+  void Apply() override;
+
+ private:
+  // Given the id of an integer constant (scalar or vector), it finds or creates
+  // the corresponding toggled constant (the integer with the same data words
+  // but opposite signedness). Returns the id of the toggled instruction if the
+  // constant is an integer scalar or vector, 0 otherwise.
+  uint32_t FindOrCreateToggledIntegerConstant(uint32_t id);
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_H_
diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
index 727132e..20575e1 100644
--- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
+++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
@@ -34,6 +34,12 @@
 
 uint32_t FuzzerPassInterchangeZeroLikeConstants::FindOrCreateToggledConstant(
     opt::Instruction* declaration) {
+  // |declaration| must not be a specialization constant because we do not know
+  // the value of specialization constants.
+  if (opt::IsSpecConstantInst(declaration->opcode())) {
+    return 0;
+  }
+
   auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(
       declaration->result_id());
 
@@ -57,24 +63,6 @@
   return 0;
 }
 
-void FuzzerPassInterchangeZeroLikeConstants::MaybeAddUseToReplace(
-    opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
-    std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
-        uses_to_replace) {
-  // Only consider this use if it is in a block
-  if (!GetIRContext()->get_instr_block(use_inst)) {
-    return;
-  }
-
-  // Get the index of the operand restricted to input operands.
-  uint32_t in_operand_index =
-      fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
-  auto id_use_descriptor =
-      MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
-  uses_to_replace->emplace_back(
-      std::make_pair(id_use_descriptor, replacement_id));
-}
-
 void FuzzerPassInterchangeZeroLikeConstants::Apply() {
   // 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
@@ -118,11 +106,11 @@
         });
   }
 
-  // Replace the ids
+  // Replace the ids if it is allowed.
   for (auto use_to_replace : uses_to_replace) {
     MaybeApplyTransformation(TransformationReplaceIdWithSynonym(
         use_to_replace.first, use_to_replace.second));
   }
 }
 }  // namespace fuzz
-}  // namespace spvtools
\ No newline at end of file
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
index 4fcc44e..ef0f765 100644
--- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
+++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
@@ -13,8 +13,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_
-#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -46,18 +46,8 @@
   // Returns the id of the toggled instruction if the constant is zero-like,
   // 0 otherwise.
   uint32_t FindOrCreateToggledConstant(opt::Instruction* declaration);
-
-  // Given an id use (described by an instruction and an index) and an id with
-  // which the original one should be replaced, adds a pair (with the elements
-  // being the corresponding id use descriptor and the replacement id) to
-  // |uses_to_replace| if the use is in an instruction block, otherwise does
-  // nothing.
-  void MaybeAddUseToReplace(
-      opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
-      std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
-          uses_to_replace);
 };
 
 }  // namespace fuzz
 }  // namespace spvtools
-#endif  // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_ZERO_LIKE_CONSTANTS_H_
diff --git a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp
new file mode 100644
index 0000000..f4f2a80
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.cpp
@@ -0,0 +1,71 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_make_vector_operations_dynamic.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_make_vector_operation_dynamic.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassMakeVectorOperationsDynamic::FuzzerPassMakeVectorOperationsDynamic(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassMakeVectorOperationsDynamic::
+    ~FuzzerPassMakeVectorOperationsDynamic() = default;
+
+void FuzzerPassMakeVectorOperationsDynamic::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        // Randomly decide whether to try applying the transformation.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()
+                    ->GetChanceOfMakingVectorOperationDynamic())) {
+          continue;
+        }
+
+        // |instruction| must be a vector operation.
+        if (!TransformationMakeVectorOperationDynamic::IsVectorOperation(
+                GetIRContext(), &instruction)) {
+          continue;
+        }
+
+        // Make sure |instruction| has only one indexing operand.
+        assert(instruction.NumInOperands() ==
+                   (instruction.opcode() == SpvOpCompositeExtract ? 2 : 3) &&
+               "FuzzerPassMakeVectorOperationsDynamic: the composite "
+               "instruction must have "
+               "only one indexing operand.");
+
+        // Applies the make vector operation dynamic transformation.
+        ApplyTransformation(TransformationMakeVectorOperationDynamic(
+            instruction.result_id(),
+            FindOrCreateIntegerConstant(
+                {instruction.GetSingleWordInOperand(
+                    instruction.opcode() == SpvOpCompositeExtract ? 1 : 2)},
+                32, GetFuzzerContext()->ChooseEven(), false)));
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h
new file mode 100644
index 0000000..dd51cde
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_
+#define SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Looks for OpCompositeExtract/Insert instructions on vectors, and replaces
+// them with OpVectorExtract/InsertDynamic.
+class FuzzerPassMakeVectorOperationsDynamic : public FuzzerPass {
+ public:
+  FuzzerPassMakeVectorOperationsDynamic(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassMakeVectorOperationsDynamic() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_MAKE_VECTOR_OPERATIONS_DYNAMIC_H_
diff --git a/source/fuzz/fuzzer_pass_merge_function_returns.cpp b/source/fuzz/fuzzer_pass_merge_function_returns.cpp
new file mode 100644
index 0000000..fc9c74d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_merge_function_returns.cpp
@@ -0,0 +1,337 @@
+// Copyright (c) 2020 Stefano Milizia
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_merge_function_returns.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_add_early_terminator_wrapper.h"
+#include "source/fuzz/transformation_merge_function_returns.h"
+#include "source/fuzz/transformation_wrap_early_terminator_in_function.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassMergeFunctionReturns::FuzzerPassMergeFunctionReturns(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassMergeFunctionReturns::~FuzzerPassMergeFunctionReturns() = default;
+
+void FuzzerPassMergeFunctionReturns::Apply() {
+  // The pass might add new functions to the module (due to wrapping early
+  // terminator instructions in function calls), so we record the functions that
+  // are currently present and then iterate over them.
+  std::vector<opt::Function*> functions;
+  for (auto& function : *GetIRContext()->module()) {
+    functions.emplace_back(&function);
+  }
+
+  for (auto* function : functions) {
+    // Randomly decide whether to consider this function.
+    if (GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfMergingFunctionReturns())) {
+      continue;
+    }
+
+    // We skip wrappers for early terminators, since this fuzzer pass introduces
+    // such wrappers to eliminate early terminators.
+    if (IsEarlyTerminatorWrapper(*function)) {
+      continue;
+    }
+
+    // Only consider functions that have early returns.
+    if (!function->HasEarlyReturn()) {
+      continue;
+    }
+
+    // Wrap early terminators in function calls.
+    ForEachInstructionWithInstructionDescriptor(
+        function,
+        [this, function](
+            opt::BasicBlock* /*unused*/, opt::BasicBlock::iterator inst_it,
+            const protobufs::InstructionDescriptor& instruction_descriptor) {
+          const SpvOp opcode = inst_it->opcode();
+          switch (opcode) {
+            case SpvOpKill:
+            case SpvOpUnreachable:
+            case SpvOpTerminateInvocation: {
+              // This is an early termination instruction - we need to wrap it
+              // so that it becomes a return.
+              if (TransformationWrapEarlyTerminatorInFunction::
+                      MaybeGetWrapperFunction(GetIRContext(), opcode) ==
+                  nullptr) {
+                // We don't have a suitable wrapper function, so create one.
+                ApplyTransformation(TransformationAddEarlyTerminatorWrapper(
+                    GetFuzzerContext()->GetFreshId(),
+                    GetFuzzerContext()->GetFreshId(), opcode));
+              }
+              // If the function has non-void return type then we need a
+              // suitable value to use in an OpReturnValue instruction.
+              opt::Instruction* function_return_type =
+                  GetIRContext()->get_def_use_mgr()->GetDef(
+                      function->type_id());
+              uint32_t returned_value_id;
+              if (function_return_type->opcode() == SpvOpTypeVoid) {
+                // No value is needed.
+                returned_value_id = 0;
+              } else if (fuzzerutil::CanCreateConstant(
+                             GetIRContext(),
+                             function_return_type->result_id())) {
+                // We favour returning an irrelevant zero.
+                returned_value_id = FindOrCreateZeroConstant(
+                    function_return_type->result_id(), true);
+              } else {
+                // It's not possible to use an irrelevant zero, so we use an
+                // OpUndef instead.
+                returned_value_id =
+                    FindOrCreateGlobalUndef(function_return_type->result_id());
+              }
+              // Wrap the early termination instruction in a function call.
+              ApplyTransformation(TransformationWrapEarlyTerminatorInFunction(
+                  GetFuzzerContext()->GetFreshId(), instruction_descriptor,
+                  returned_value_id));
+              break;
+            }
+            default:
+              break;
+          }
+        });
+
+    // Get the return blocks.
+    auto return_blocks = fuzzerutil::GetReachableReturnBlocks(
+        GetIRContext(), function->result_id());
+
+    // Only go ahead if there is more than one reachable return block.
+    if (return_blocks.size() <= 1) {
+      continue;
+    }
+
+    // Make sure that OpConstantTrue and OpConstantFalse are in the module.
+    FindOrCreateBoolConstant(true, false);
+    FindOrCreateBoolConstant(false, false);
+
+    // Collect the ids available after the entry block of the function.
+    auto ids_available_after_entry_block =
+        GetTypesToIdsAvailableAfterEntryBlock(function);
+
+    // If the entry block does not branch unconditionally to another block,
+    // split it.
+    if (function->entry()->terminator()->opcode() != SpvOpBranch) {
+      SplitBlockAfterOpPhiOrOpVariable(function->entry()->id());
+    }
+
+    // Collect the merge blocks of the function whose corresponding loops
+    // contain return blocks.
+    auto merge_blocks = GetMergeBlocksOfLoopsContainingBlocks(return_blocks);
+
+    // Split the merge blocks, if they contain instructions different from
+    // OpLabel, OpPhi and OpBranch. Collect the new ids of merge blocks.
+    std::vector<uint32_t> actual_merge_blocks;
+    for (uint32_t merge_block : merge_blocks) {
+      opt::BasicBlock* block = GetIRContext()->get_instr_block(merge_block);
+
+      // We don't need to split blocks that are already suitable (they only
+      // contain OpLabel, OpPhi or OpBranch instructions).
+      if (GetIRContext()
+              ->get_instr_block(merge_block)
+              ->WhileEachInst([](opt::Instruction* inst) {
+                return inst->opcode() == SpvOpLabel ||
+                       inst->opcode() == SpvOpPhi ||
+                       inst->opcode() == SpvOpBranch;
+              })) {
+        actual_merge_blocks.emplace_back(merge_block);
+        continue;
+      }
+
+      // If the merge block is also a loop header, we need to add a preheader,
+      // which will be the new merge block.
+      if (block->IsLoopHeader()) {
+        actual_merge_blocks.emplace_back(
+            GetOrCreateSimpleLoopPreheader(merge_block)->id());
+        continue;
+      }
+
+      // If the merge block is not a loop header, we must split it after the
+      // last OpPhi instruction. The merge block will be the first of the pair
+      // of blocks obtained after splitting, and it keeps the original id.
+      SplitBlockAfterOpPhiOrOpVariable(merge_block);
+      actual_merge_blocks.emplace_back(merge_block);
+    }
+
+    // Get the ids needed by the transformation.
+    uint32_t outer_header_id = GetFuzzerContext()->GetFreshId();
+    uint32_t outer_return_id = GetFuzzerContext()->GetFreshId();
+
+    bool function_is_void =
+        GetIRContext()->get_type_mgr()->GetType(function->type_id())->AsVoid();
+
+    // We only need a return value if the function is not void.
+    uint32_t return_val_id =
+        function_is_void ? 0 : GetFuzzerContext()->GetFreshId();
+
+    // We only need a placeholder for the return value if the function is not
+    // void and there is at least one relevant merge block.
+    uint32_t returnable_val_id = 0;
+    if (!function_is_void && !actual_merge_blocks.empty()) {
+      // If there is an id of the suitable type, choose one at random.
+      if (ids_available_after_entry_block.count(function->type_id())) {
+        const auto& candidates =
+            ids_available_after_entry_block[function->type_id()];
+        returnable_val_id =
+            candidates[GetFuzzerContext()->RandomIndex(candidates)];
+      } else {
+        // If there is no id, add a global OpUndef.
+        uint32_t suitable_id = FindOrCreateGlobalUndef(function->type_id());
+        // Add the new id to the map of available ids.
+        ids_available_after_entry_block.emplace(
+            function->type_id(), std::vector<uint32_t>({suitable_id}));
+        returnable_val_id = suitable_id;
+      }
+    }
+
+    // Collect all the ids needed for merge blocks.
+    auto merge_blocks_info = GetInfoNeededForMergeBlocks(
+        actual_merge_blocks, &ids_available_after_entry_block);
+
+    // 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));
+  }
+}
+
+std::map<uint32_t, std::vector<uint32_t>>
+FuzzerPassMergeFunctionReturns::GetTypesToIdsAvailableAfterEntryBlock(
+    opt::Function* function) const {
+  std::map<uint32_t, std::vector<uint32_t>> result;
+  // Consider all global declarations
+  for (auto& global : GetIRContext()->module()->types_values()) {
+    if (global.HasResultId() && global.type_id()) {
+      if (!result.count(global.type_id())) {
+        result.emplace(global.type_id(), std::vector<uint32_t>());
+      }
+      result[global.type_id()].emplace_back(global.result_id());
+    }
+  }
+
+  // Consider all function parameters
+  function->ForEachParam([&result](opt::Instruction* param) {
+    if (param->HasResultId() && param->type_id()) {
+      if (!result.count(param->type_id())) {
+        result.emplace(param->type_id(), std::vector<uint32_t>());
+      }
+
+      result[param->type_id()].emplace_back(param->result_id());
+    }
+  });
+
+  // Consider all the instructions in the entry block.
+  for (auto& inst : *function->entry()) {
+    if (inst.HasResultId() && inst.type_id()) {
+      if (!result.count(inst.type_id())) {
+        result.emplace(inst.type_id(), std::vector<uint32_t>());
+      }
+      result[inst.type_id()].emplace_back(inst.result_id());
+    }
+  }
+
+  return result;
+}
+
+std::set<uint32_t>
+FuzzerPassMergeFunctionReturns::GetMergeBlocksOfLoopsContainingBlocks(
+    const std::set<uint32_t>& blocks) const {
+  std::set<uint32_t> result;
+  for (uint32_t block : blocks) {
+    uint32_t merge_block =
+        GetIRContext()->GetStructuredCFGAnalysis()->LoopMergeBlock(block);
+
+    while (merge_block != 0 && !result.count(merge_block)) {
+      // Add a new entry.
+      result.emplace(merge_block);
+
+      // Walk up the loop tree.
+      block = merge_block;
+      merge_block = GetIRContext()->GetStructuredCFGAnalysis()->LoopMergeBlock(
+          merge_block);
+    }
+  }
+
+  return result;
+}
+
+std::vector<protobufs::ReturnMergingInfo>
+FuzzerPassMergeFunctionReturns::GetInfoNeededForMergeBlocks(
+    const std::vector<uint32_t>& merge_blocks,
+    std::map<uint32_t, std::vector<uint32_t>>*
+        ids_available_after_entry_block) {
+  std::vector<protobufs::ReturnMergingInfo> result;
+  for (uint32_t merge_block : merge_blocks) {
+    protobufs::ReturnMergingInfo info;
+    info.set_merge_block_id(merge_block);
+    info.set_is_returning_id(this->GetFuzzerContext()->GetFreshId());
+    info.set_maybe_return_val_id(this->GetFuzzerContext()->GetFreshId());
+
+    // Add all the ids needed for the OpPhi instructions.
+    this->GetIRContext()
+        ->get_instr_block(merge_block)
+        ->ForEachPhiInst([this, &info, &ids_available_after_entry_block](
+                             opt::Instruction* phi_inst) {
+          protobufs::UInt32Pair entry;
+          entry.set_first(phi_inst->result_id());
+
+          // If there is an id of the suitable type, choose one at random.
+          if (ids_available_after_entry_block->count(phi_inst->type_id())) {
+            auto& candidates =
+                ids_available_after_entry_block->at(phi_inst->type_id());
+            entry.set_second(
+                candidates[this->GetFuzzerContext()->RandomIndex(candidates)]);
+          } else {
+            // If there is no id, add a global OpUndef.
+            uint32_t suitable_id =
+                this->FindOrCreateGlobalUndef(phi_inst->type_id());
+            // Add the new id to the map of available ids.
+            ids_available_after_entry_block->emplace(
+                phi_inst->type_id(), std::vector<uint32_t>({suitable_id}));
+            entry.set_second(suitable_id);
+          }
+
+          // Add the entry to the list.
+          *info.add_opphi_to_suitable_id() = entry;
+        });
+
+    result.emplace_back(info);
+  }
+
+  return result;
+}
+
+bool FuzzerPassMergeFunctionReturns::IsEarlyTerminatorWrapper(
+    const opt::Function& function) const {
+  for (SpvOp opcode : {SpvOpKill, SpvOpUnreachable, SpvOpTerminateInvocation}) {
+    if (TransformationWrapEarlyTerminatorInFunction::MaybeGetWrapperFunction(
+            GetIRContext(), opcode) == &function) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_merge_function_returns.h b/source/fuzz/fuzzer_pass_merge_function_returns.h
new file mode 100644
index 0000000..3b5a668
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_merge_function_returns.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2020 Stefano Milizia
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MERGE_FUNCTION_RETURNS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_MERGE_FUNCTION_RETURNS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass for changing functions in the module so that they don't have an
+// early return.  When handling a function the pass first eliminates early
+// terminator instructions, such as OpKill, by wrapping them in functions and
+// replacing them with a function call followed by a return.  The return
+// instructions that arise are then modified so that the function does not have
+// early returns.
+class FuzzerPassMergeFunctionReturns : public FuzzerPass {
+ public:
+  FuzzerPassMergeFunctionReturns(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassMergeFunctionReturns();
+
+  void Apply() override;
+
+ private:
+  // Returns a map from type ids to a list of ids with that type and which are
+  // available at the end of the entry block of |function|.
+  std::map<uint32_t, std::vector<uint32_t>>
+  GetTypesToIdsAvailableAfterEntryBlock(opt::Function* function) const;
+
+  // Returns the set of all the loop merge blocks whose corresponding loops
+  // contain at least one of the blocks in |blocks|.
+  std::set<uint32_t> GetMergeBlocksOfLoopsContainingBlocks(
+      const std::set<uint32_t>& blocks) const;
+
+  // Returns a list of ReturnMergingInfo messages, containing the information
+  // needed by the transformation for each of the relevant merge blocks.
+  // If a new id is created (because |ids_available_after_entry_block| does not
+  // have an entry for the corresponding type), a new entry is added to
+  // |ids_available_after_entry_block|, mapping its type to a singleton set
+  // containing it.
+  std::vector<protobufs::ReturnMergingInfo> GetInfoNeededForMergeBlocks(
+      const std::vector<uint32_t>& merge_blocks,
+      std::map<uint32_t, std::vector<uint32_t>>*
+          ids_available_after_entry_block);
+
+  // Returns true if and only if |function| is a wrapper for an early terminator
+  // instruction such as OpKill.
+  bool IsEarlyTerminatorWrapper(const opt::Function& function) const;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_MERGE_FUNCTION_RETURNS_H_
diff --git a/source/fuzz/fuzzer_pass_mutate_pointers.cpp b/source/fuzz/fuzzer_pass_mutate_pointers.cpp
new file mode 100644
index 0000000..89f5f5c
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_mutate_pointers.cpp
@@ -0,0 +1,74 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_mutate_pointers.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_mutate_pointer.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassMutatePointers::FuzzerPassMutatePointers(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassMutatePointers::~FuzzerPassMutatePointers() = default;
+
+void FuzzerPassMutatePointers::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* function, opt::BasicBlock* block,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& instruction_descriptor) {
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfMutatingPointer())) {
+          return;
+        }
+
+        if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) {
+          return;
+        }
+
+        auto available_pointers = FindAvailableInstructions(
+            function, block, inst_it,
+            [](opt::IRContext* ir_context, opt::Instruction* inst) {
+              return TransformationMutatePointer::IsValidPointerInstruction(
+                  ir_context, *inst);
+            });
+
+        if (available_pointers.empty()) {
+          return;
+        }
+
+        const auto* pointer_inst =
+            available_pointers[GetFuzzerContext()->RandomIndex(
+                available_pointers)];
+
+        // Make sure there is an irrelevant constant in the module.
+        FindOrCreateZeroConstant(fuzzerutil::GetPointeeTypeIdFromPointerType(
+                                     GetIRContext(), pointer_inst->type_id()),
+                                 true);
+
+        ApplyTransformation(TransformationMutatePointer(
+            pointer_inst->result_id(), GetFuzzerContext()->GetFreshId(),
+            instruction_descriptor));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_mutate_pointers.h b/source/fuzz/fuzzer_pass_mutate_pointers.h
new file mode 100644
index 0000000..f77523e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_mutate_pointers.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MUTATE_POINTERS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_MUTATE_POINTERS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly mutates the value of each pointer instruction in the module.
+class FuzzerPassMutatePointers : public FuzzerPass {
+ public:
+  FuzzerPassMutatePointers(opt::IRContext* ir_context,
+                           TransformationContext* transformation_context,
+                           FuzzerContext* fuzzer_context,
+                           protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassMutatePointers() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_MUTATE_POINTERS_H_
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
index 2775bb8..d87662e 100644
--- a/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.cpp
@@ -347,8 +347,7 @@
   auto uniform_descriptors =
       GetTransformationContext()
           ->GetFactManager()
-          ->GetUniformDescriptorsForConstant(GetIRContext(),
-                                             constant_use.id_of_interest());
+          ->GetUniformDescriptorsForConstant(constant_use.id_of_interest());
   if (uniform_descriptors.empty()) {
     // No relevant uniforms, so do not obfuscate.
     return;
diff --git a/source/fuzz/fuzzer_pass_obfuscate_constants.h b/source/fuzz/fuzzer_pass_obfuscate_constants.h
index 52d8efe..d48b37f 100644
--- a/source/fuzz/fuzzer_pass_obfuscate_constants.h
+++ b/source/fuzz/fuzzer_pass_obfuscate_constants.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
-#define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_H_
 
 #include <vector>
 
@@ -109,4 +109,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_OBFUSCATE_CONSTANTS_H_
diff --git a/source/fuzz/fuzzer_pass_outline_functions.cpp b/source/fuzz/fuzzer_pass_outline_functions.cpp
index 1cd291e..4210125 100644
--- a/source/fuzz/fuzzer_pass_outline_functions.cpp
+++ b/source/fuzz/fuzzer_pass_outline_functions.cpp
@@ -47,30 +47,13 @@
     for (auto& block : *function) {
       blocks.push_back(&block);
     }
-    auto entry_block = blocks[GetFuzzerContext()->RandomIndex(blocks)];
+    auto entry_block = MaybeGetEntryBlockSuitableForOutlining(
+        blocks[GetFuzzerContext()->RandomIndex(blocks)]);
 
-    // If the entry block starts with OpPhi, try to split it.
-    if (entry_block->begin()->opcode() == SpvOpPhi) {
-      // Find the first non-OpPhi instruction.
-      opt::Instruction* non_phi_inst;
-      for (auto instruction : *entry_block) {
-        if (instruction.opcode() != SpvOpPhi) {
-          non_phi_inst = &instruction;
-          break;
-        }
-      }
-
-      // If the split was not applicable, the transformation will not work.
-      uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
-      if (!MaybeApplyTransformation(TransformationSplitBlock(
-              MakeInstructionDescriptor(non_phi_inst->result_id(),
-                                        non_phi_inst->opcode(), 0),
-              new_block_id))) {
-        return;
-      }
-
-      // The new entry block is the newly-created block.
-      entry_block = &*function->FindBlock(new_block_id);
+    if (!entry_block) {
+      // The chosen block is not suitable to be the entry block of a region that
+      // will be outlined.
+      continue;
     }
 
     auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(function);
@@ -81,16 +64,26 @@
          postdominates_entry_block != nullptr;
          postdominates_entry_block = postdominator_analysis->ImmediateDominator(
              postdominates_entry_block)) {
+      // Consider the block if it is dominated by the entry block, ignore it if
+      // it is a continue target.
       if (dominator_analysis->Dominates(entry_block,
-                                        postdominates_entry_block)) {
+                                        postdominates_entry_block) &&
+          !GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock(
+              postdominates_entry_block->id())) {
         candidate_exit_blocks.push_back(postdominates_entry_block);
       }
     }
     if (candidate_exit_blocks.empty()) {
       continue;
     }
-    auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex(
-        candidate_exit_blocks)];
+    auto exit_block = MaybeGetExitBlockSuitableForOutlining(
+        candidate_exit_blocks[GetFuzzerContext()->RandomIndex(
+            candidate_exit_blocks)]);
+
+    if (!exit_block) {
+      // The block chosen is not suitable
+      continue;
+    }
 
     auto region_blocks = TransformationOutlineFunction::GetRegionBlocks(
         GetIRContext(), entry_block, exit_block);
@@ -114,11 +107,90 @@
         GetFuzzerContext()->GetFreshId(),
         /*new_caller_result_id*/ GetFuzzerContext()->GetFreshId(),
         /*new_callee_result_id*/ GetFuzzerContext()->GetFreshId(),
-        /*input_id_to_fresh_id*/ std::move(input_id_to_fresh_id),
-        /*output_id_to_fresh_id*/ std::move(output_id_to_fresh_id));
+        /*input_id_to_fresh_id*/ input_id_to_fresh_id,
+        /*output_id_to_fresh_id*/ output_id_to_fresh_id);
     MaybeApplyTransformation(transformation);
   }
 }
 
+opt::BasicBlock*
+FuzzerPassOutlineFunctions::MaybeGetEntryBlockSuitableForOutlining(
+    opt::BasicBlock* entry_block) {
+  // If the entry block is a loop header, we need to get or create its
+  // preheader and make it the entry block, if possible.
+  if (entry_block->IsLoopHeader()) {
+    auto predecessors =
+        GetIRContext()->cfg()->preds(entry_block->GetLabel()->result_id());
+
+    if (predecessors.size() < 2) {
+      // The header only has one predecessor (the back-edge block) and thus
+      // it is unreachable. The block cannot be adjusted to be suitable for
+      // outlining.
+      return nullptr;
+    }
+
+    // Get or create a suitable preheader and make it become the entry block.
+    entry_block =
+        GetOrCreateSimpleLoopPreheader(entry_block->GetLabel()->result_id());
+  }
+
+  assert(!entry_block->IsLoopHeader() &&
+         "The entry block cannot be a loop header at this point.");
+
+  // If the entry block starts with OpPhi or OpVariable, try to split it.
+  if (entry_block->begin()->opcode() == SpvOpPhi ||
+      entry_block->begin()->opcode() == SpvOpVariable) {
+    // Find the first non-OpPhi and non-OpVariable instruction.
+    auto non_phi_or_var_inst = &*entry_block->begin();
+    while (non_phi_or_var_inst->opcode() == SpvOpPhi ||
+           non_phi_or_var_inst->opcode() == SpvOpVariable) {
+      non_phi_or_var_inst = non_phi_or_var_inst->NextNode();
+    }
+
+    // Split the block.
+    uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
+    ApplyTransformation(TransformationSplitBlock(
+        MakeInstructionDescriptor(GetIRContext(), non_phi_or_var_inst),
+        new_block_id));
+
+    // The new entry block is the newly-created block.
+    entry_block = &*entry_block->GetParent()->FindBlock(new_block_id);
+  }
+
+  return entry_block;
+}
+
+opt::BasicBlock*
+FuzzerPassOutlineFunctions::MaybeGetExitBlockSuitableForOutlining(
+    opt::BasicBlock* exit_block) {
+  // The exit block must not be a continue target.
+  assert(!GetIRContext()->GetStructuredCFGAnalysis()->IsContinueBlock(
+             exit_block->id()) &&
+         "A candidate exit block cannot be a continue target.");
+
+  // If the exit block is a merge block, try to split it and return the second
+  // block in the pair as the exit block.
+  if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(
+          exit_block->id())) {
+    uint32_t new_block_id = GetFuzzerContext()->GetFreshId();
+
+    // Find the first non-OpPhi instruction, after which to split.
+    auto split_before = &*exit_block->begin();
+    while (split_before->opcode() == SpvOpPhi) {
+      split_before = split_before->NextNode();
+    }
+
+    if (!MaybeApplyTransformation(TransformationSplitBlock(
+            MakeInstructionDescriptor(GetIRContext(), split_before),
+            new_block_id))) {
+      return nullptr;
+    }
+
+    return &*exit_block->GetParent()->FindBlock(new_block_id);
+  }
+
+  return exit_block;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_outline_functions.h b/source/fuzz/fuzzer_pass_outline_functions.h
index 6532ed9..02022aa 100644
--- a/source/fuzz/fuzzer_pass_outline_functions.h
+++ b/source/fuzz/fuzzer_pass_outline_functions.h
@@ -32,6 +32,31 @@
   ~FuzzerPassOutlineFunctions();
 
   void Apply() override;
+
+  // Returns a block suitable to be an entry block for a region that can be
+  // outlined, i.e. a block that is not a loop header and that does not start
+  // with OpPhi or OpVariable. In particular, it returns:
+  // - |entry_block| if it is suitable
+  // - otherwise, a block found by:
+  //   - looking for or creating a new preheader, if |entry_block| is a loop
+  //     header
+  //   - splitting the candidate entry block, if it starts with OpPhi or
+  //     OpVariable.
+  // Returns nullptr if a suitable block cannot be found following the
+  // instructions above.
+  opt::BasicBlock* MaybeGetEntryBlockSuitableForOutlining(
+      opt::BasicBlock* entry_block);
+
+  // Returns:
+  // - |exit_block| if it is not a merge block
+  // - the second block obtained by splitting |exit_block|, if |exit_block| is a
+  //   merge block.
+  // Assumes that |exit_block| is not a continue target.
+  // The block returned by this function should be suitable to be the exit block
+  // of a region that can be outlined.
+  // Returns nullptr if |exit_block| is a merge block and it cannot be split.
+  opt::BasicBlock* MaybeGetExitBlockSuitableForOutlining(
+      opt::BasicBlock* exit_block);
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_pass_permute_blocks.h b/source/fuzz/fuzzer_pass_permute_blocks.h
index f2d3b39..e5a672c 100644
--- a/source/fuzz/fuzzer_pass_permute_blocks.h
+++ b/source/fuzz/fuzzer_pass_permute_blocks.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
-#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -37,4 +37,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_BLOCKS_H_
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
index e15aef6..de6b03f 100644
--- a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
+
 #include <numeric>
 #include <vector>
 
 #include "source/fuzz/fuzzer_context.h"
-#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/transformation_permute_function_parameters.h"
diff --git a/source/fuzz/fuzzer_pass_permute_instructions.cpp b/source/fuzz/fuzzer_pass_permute_instructions.cpp
new file mode 100644
index 0000000..6867053
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_instructions.cpp
@@ -0,0 +1,64 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_instructions.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_move_instruction_down.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermuteInstructions::FuzzerPassPermuteInstructions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassPermuteInstructions::~FuzzerPassPermuteInstructions() = default;
+
+void FuzzerPassPermuteInstructions::Apply() {
+  // We are iterating over all instructions in all basic blocks.
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // We need to collect all instructions in the block into a separate vector
+      // since application of the transformation below might invalidate
+      // iterators.
+      std::vector<opt::Instruction*> instructions;
+      for (auto& instruction : block) {
+        instructions.push_back(&instruction);
+      }
+
+      // We consider all instructions in reverse to increase the possible number
+      // of applied transformations.
+      for (auto it = instructions.rbegin(); it != instructions.rend(); ++it) {
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfPermutingInstructions())) {
+          continue;
+        }
+
+        while (MaybeApplyTransformation(TransformationMoveInstructionDown(
+            MakeInstructionDescriptor(GetIRContext(), *it)))) {
+          // Apply the transformation as many times as possible.
+        }
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_instructions.h b/source/fuzz/fuzzer_pass_permute_instructions.h
new file mode 100644
index 0000000..e02ddfa
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_instructions.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_INSTRUCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Permutes instructions in every block of all while preserving the module's
+// semantics.
+class FuzzerPassPermuteInstructions : public FuzzerPass {
+ public:
+  FuzzerPassPermuteInstructions(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPermuteInstructions() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_INSTRUCTIONS_H_
diff --git a/source/fuzz/fuzzer_pass_permute_phi_operands.cpp b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
index c241d9d..c379c53 100644
--- a/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
+++ b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
+
 #include <numeric>
 #include <vector>
 
 #include "source/fuzz/fuzzer_context.h"
-#include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/transformation_permute_phi_operands.h"
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp b/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
new file mode 100644
index 0000000..7a115ae
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_down.cpp
@@ -0,0 +1,68 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_propagate_instructions_down.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/transformation_propagate_instruction_down.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPropagateInstructionsDown::FuzzerPassPropagateInstructionsDown(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassPropagateInstructionsDown::~FuzzerPassPropagateInstructionsDown() =
+    default;
+
+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)) {
+        reachable_blocks.push_back(&block);
+      }
+    }
+
+    for (const auto* block : reachable_blocks) {
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfPropagatingInstructionsDown())) {
+        continue;
+      }
+
+      if (TransformationPropagateInstructionDown::IsApplicableToBlock(
+              GetIRContext(), block->id())) {
+        // Record fresh ids for every successor of the |block| that we can
+        // propagate an instruction into.
+        std::map<uint32_t, uint32_t> fresh_ids;
+        for (auto id :
+             TransformationPropagateInstructionDown::GetAcceptableSuccessors(
+                 GetIRContext(), block->id())) {
+          fresh_ids[id] = GetFuzzerContext()->GetFreshId();
+        }
+
+        ApplyTransformation(TransformationPropagateInstructionDown(
+            block->id(), GetFuzzerContext()->GetFreshId(), fresh_ids));
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_down.h b/source/fuzz/fuzzer_pass_propagate_instructions_down.h
new file mode 100644
index 0000000..536bf00
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_down.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_PROPAGATE_INSTRUCTIONS_DOWN_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_DOWN_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly propagates instructions from some block into the block's successors.
+class FuzzerPassPropagateInstructionsDown : public FuzzerPass {
+ public:
+  FuzzerPassPropagateInstructionsDown(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPropagateInstructionsDown() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_DOWN_H_
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp b/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp
new file mode 100644
index 0000000..16ec680
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_up.cpp
@@ -0,0 +1,63 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_propagate_instructions_up.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_propagate_instruction_up.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPropagateInstructionsUp::FuzzerPassPropagateInstructionsUp(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassPropagateInstructionsUp::~FuzzerPassPropagateInstructionsUp() =
+    default;
+
+void FuzzerPassPropagateInstructionsUp::Apply() {
+  for (const auto& function : *GetIRContext()->module()) {
+    for (const auto& block : function) {
+      if (!GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()->GetChanceOfPropagatingInstructionsUp())) {
+        continue;
+      }
+
+      if (TransformationPropagateInstructionUp::IsApplicableToBlock(
+              GetIRContext(), block.id())) {
+        std::map<uint32_t, uint32_t> fresh_ids;
+        for (auto id : GetIRContext()->cfg()->preds(block.id())) {
+          auto& fresh_id = fresh_ids[id];
+
+          if (!fresh_id) {
+            // Create a fresh id if it hasn't been created yet. |fresh_id| will
+            // be default-initialized to 0 in this case.
+            fresh_id = GetFuzzerContext()->GetFreshId();
+          }
+        }
+        ApplyTransformation(
+            TransformationPropagateInstructionUp(block.id(), fresh_ids));
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_propagate_instructions_up.h b/source/fuzz/fuzzer_pass_propagate_instructions_up.h
new file mode 100644
index 0000000..d915b31
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_propagate_instructions_up.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_PROPAGATE_INSTRUCTIONS_UP_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Decides whether to propagate instructions from some block into its
+// predecessors.
+class FuzzerPassPropagateInstructionsUp : public FuzzerPass {
+ public:
+  FuzzerPassPropagateInstructionsUp(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPropagateInstructionsUp() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PROPAGATE_INSTRUCTIONS_UP_H_
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
new file mode 100644
index 0000000..139dc6e
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
@@ -0,0 +1,79 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_replace_adds_subs_muls_with_carrying_extended.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+const uint32_t kArithmeticInstructionIndexLeftInOperand = 0;
+}  // namespace
+
+FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
+    FuzzerPassReplaceAddsSubsMulsWithCarryingExtended(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
+    ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default;
+
+void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() {
+  std::vector<opt::Instruction> instructions_for_transformation;
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        // Randomly decide whether to apply the transformation.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()
+                    ->GetChanceOfReplacingAddSubMulWithCarryingExtended())) {
+          continue;
+        }
+
+        // Check if the transformation can be applied to this instruction.
+        if (!TransformationReplaceAddSubMulWithCarryingExtended::
+                IsInstructionSuitable(GetIRContext(), instruction)) {
+          continue;
+        }
+        instructions_for_transformation.push_back(instruction);
+      }
+    }
+  }
+  for (auto& instruction : instructions_for_transformation) {
+    // Get the operand type id. We know that both operands have the same
+    // type.
+    uint32_t operand_type_id =
+        GetIRContext()
+            ->get_def_use_mgr()
+            ->GetDef(instruction.GetSingleWordInOperand(
+                kArithmeticInstructionIndexLeftInOperand))
+            ->type_id();
+
+    // Ensure the required struct type exists. The struct type is based on
+    // the operand type.
+    FindOrCreateStructType({operand_type_id, operand_type_id});
+
+    ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended(
+        GetFuzzerContext()->GetFreshId(), instruction.result_id()));
+  }
+}
+}  // namespace fuzz
+}  // namespace spvtools
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
new file mode 100644
index 0000000..dd39e6b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that replaces instructions OpIAdd, OpISub, OpIMul with pairs of
+// instructions. The first one (OpIAddCarry, OpISubBorrow, OpUMulExtended,
+// OpSMulExtended) computes the result into a struct. The second one extracts
+// the appropriate component from the struct to yield the original result.
+class FuzzerPassReplaceAddsSubsMulsWithCarryingExtended : public FuzzerPass {
+ public:
+  FuzzerPassReplaceAddsSubsMulsWithCarryingExtended(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H_
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
new file mode 100644
index 0000000..e6bebea
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_replace_branches_from_dead_blocks_with_exits.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassReplaceBranchesFromDeadBlocksWithExits::
+    FuzzerPassReplaceBranchesFromDeadBlocksWithExits(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceBranchesFromDeadBlocksWithExits::
+    ~FuzzerPassReplaceBranchesFromDeadBlocksWithExits() = default;
+
+void FuzzerPassReplaceBranchesFromDeadBlocksWithExits::Apply() {
+  // OpKill can only be used as a terminator in a function that is guaranteed
+  // to be executed with the Fragment execution model.  We conservatively only
+  // allow OpKill if every entry point in the module has the Fragment execution
+  // model.
+  auto fragment_execution_model_guaranteed =
+      std::all_of(GetIRContext()->module()->entry_points().begin(),
+                  GetIRContext()->module()->entry_points().end(),
+                  [](const opt::Instruction& entry_point) -> bool {
+                    return entry_point.GetSingleWordInOperand(0) ==
+                           SpvExecutionModelFragment;
+                  });
+
+  // Transformations of this type can disable one another.  To avoid ordering
+  // bias, we therefore build a set of candidate transformations to apply, and
+  // subsequently apply them in a random order, skipping any that cease to be
+  // applicable.
+  std::vector<TransformationReplaceBranchFromDeadBlockWithExit>
+      candidate_transformations;
+
+  // Consider every block in every function.
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // Probabilistically decide whether to skip this block.
+      if (GetFuzzerContext()->ChoosePercentage(
+              GetFuzzerContext()
+                  ->GetChanceOfReplacingBranchFromDeadBlockWithExit())) {
+        continue;
+      }
+      // Check whether the block is suitable for having its terminator replaced.
+      if (!TransformationReplaceBranchFromDeadBlockWithExit::BlockIsSuitable(
+              GetIRContext(), *GetTransformationContext(), block)) {
+        continue;
+      }
+      // We can always use OpUnreachable to replace a block's terminator.
+      // Whether we can use OpKill depends on the execution model, and which of
+      // OpReturn and OpReturnValue we can use depends on the return type of the
+      // enclosing function.
+      std::vector<SpvOp> opcodes = {SpvOpUnreachable};
+      if (fragment_execution_model_guaranteed) {
+        opcodes.emplace_back(SpvOpKill);
+      }
+      auto function_return_type =
+          GetIRContext()->get_type_mgr()->GetType(function.type_id());
+      if (function_return_type->AsVoid()) {
+        opcodes.emplace_back(SpvOpReturn);
+      } else if (fuzzerutil::CanCreateConstant(GetIRContext(),
+                                               function.type_id())) {
+        // For simplicity we only allow OpReturnValue if the function return
+        // type is a type for which we can create a constant.  This allows us a
+        // zero of the given type as a default return value.
+        opcodes.emplace_back(SpvOpReturnValue);
+      }
+      // Choose one of the available terminator opcodes at random and create a
+      // candidate transformation.
+      auto opcode = opcodes[GetFuzzerContext()->RandomIndex(opcodes)];
+      candidate_transformations.emplace_back(
+          TransformationReplaceBranchFromDeadBlockWithExit(
+              block.id(), opcode,
+              opcode == SpvOpReturnValue
+                  ? FindOrCreateZeroConstant(function.type_id(), true)
+                  : 0));
+    }
+  }
+
+  // Process the candidate transformations in a random order.
+  while (!candidate_transformations.empty()) {
+    // Transformations of this type can disable one another.  For example,
+    // suppose we have dead blocks A, B, C, D arranged as follows:
+    //
+    //         A         |
+    //        / \        |
+    //       B   C       |
+    //        \ /        |
+    //         D         |
+    //
+    // Here we can replace the terminator of either B or C with an early exit,
+    // because D has two predecessors.  But if we replace the terminator of B,
+    // say, we get:
+    //
+    //         A         |
+    //        / \        |
+    //       B   C       |
+    //          /        |
+    //         D         |
+    //
+    // and now it is no longer OK to replace the terminator of C as D only has
+    // one predecessor and we do not want to make D unreachable in the control
+    // flow graph.
+    MaybeApplyTransformation(
+        GetFuzzerContext()->RemoveAtRandomIndex(&candidate_transformations));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
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
new file mode 100644
index 0000000..62164b3
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPLACE_BRANCHES_FROM_DEAD_BLOCKS_WITH_EXITS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_BRANCHES_FROM_DEAD_BLOCKS_WITH_EXITS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that, under the right conditions, replaces branch instructions
+// from dead blocks with non-branching "exit" terminators, such as OpKill and
+// OpReturn.
+class FuzzerPassReplaceBranchesFromDeadBlocksWithExits : public FuzzerPass {
+ public:
+  FuzzerPassReplaceBranchesFromDeadBlocksWithExits(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceBranchesFromDeadBlocksWithExits() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_BRANCHES_FROM_DEAD_BLOCKS_WITH_EXITS_H_
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 2a89006..9c24ac7 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
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H
-#define SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H
+#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -38,4 +38,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_MEMORIES_WITH_LOADS_STORES_H_
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 e21ea5e..e372924 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
@@ -66,7 +66,13 @@
                                       ? SpvStorageClassPrivate
                                       : SpvStorageClassFunction;
 
-    // Find or create a constant to initialize the variable from.
+    // Find or create a constant to initialize the variable from. The type of
+    // |instruction| must be such that the function FindOrCreateConstant can be
+    // called.
+    if (!fuzzerutil::CanCreateConstant(GetIRContext(),
+                                       instruction->type_id())) {
+      return;
+    }
     auto variable_initializer_id =
         FindOrCreateZeroConstant(instruction->type_id(), false);
 
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 15ec10b..ae03a45 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
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H
-#define SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H
+#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -38,4 +38,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_COPY_OBJECTS_WITH_STORES_LOADS_H_
diff --git a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
new file mode 100644
index 0000000..432addb
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.cpp
@@ -0,0 +1,183 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_replace_irrelevant_ids.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/transformation_replace_irrelevant_id.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that, for every use of an id that has been recorded as
+// irrelevant, randomly decides whether to replace it with another id of the
+// same type.
+FuzzerPassReplaceIrrelevantIds::FuzzerPassReplaceIrrelevantIds(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceIrrelevantIds::~FuzzerPassReplaceIrrelevantIds() = default;
+
+void FuzzerPassReplaceIrrelevantIds::Apply() {
+  // Keep track of the irrelevant ids. This includes all the ids that are
+  // irrelevant according to the fact manager and that are still present in the
+  // module (some of them may have been removed by previously-run
+  // transformations).
+  std::vector<uint32_t> irrelevant_ids;
+
+  // Keep a map from the type ids of irrelevant ids to all the ids with that
+  // type.
+  std::unordered_map<uint32_t, std::vector<uint32_t>> types_to_ids;
+
+  // Find all the irrelevant ids that still exist in the module and all the
+  // types for which irrelevant ids exist.
+  for (auto id :
+       GetTransformationContext()->GetFactManager()->GetIrrelevantIds()) {
+    // Check that the id still exists in the module.
+    auto declaration = GetIRContext()->get_def_use_mgr()->GetDef(id);
+    if (!declaration) {
+      continue;
+    }
+
+    irrelevant_ids.push_back(id);
+
+    // If the type of this id has not been seen before, add a mapping from this
+    // type id to an empty list in |types_to_ids|. The list will be filled later
+    // on.
+    if (types_to_ids.count(declaration->type_id()) == 0) {
+      types_to_ids.insert({declaration->type_id(), {}});
+    }
+  }
+
+  // If no irrelevant ids were found, return.
+  if (irrelevant_ids.empty()) {
+    return;
+  }
+
+  // For every type for which we have at least one irrelevant id, record all ids
+  // in the module which have that type. Skip ids of OpFunction instructions as
+  // we cannot use these as replacements.
+  for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
+    uint32_t type_id = pair.second->type_id();
+    if (pair.second->opcode() != SpvOpFunction && type_id &&
+        types_to_ids.count(type_id)) {
+      types_to_ids[type_id].push_back(pair.first);
+    }
+  }
+
+  // Keep a list of all the transformations to perform. We avoid applying the
+  // transformations while traversing the uses since applying the transformation
+  // invalidates all analyses, and we want to avoid invalidating and recomputing
+  // them every time.
+  std::vector<TransformationReplaceIrrelevantId> transformations_to_apply;
+
+  // Loop through all the uses of irrelevant ids, check that the id can be
+  // replaced and randomly decide whether to apply the transformation.
+  for (auto irrelevant_id : irrelevant_ids) {
+    uint32_t type_id =
+        GetIRContext()->get_def_use_mgr()->GetDef(irrelevant_id)->type_id();
+
+    GetIRContext()->get_def_use_mgr()->ForEachUse(
+        irrelevant_id, [this, &irrelevant_id, &type_id, &types_to_ids,
+                        &transformations_to_apply](opt::Instruction* use_inst,
+                                                   uint32_t use_index) {
+          // Randomly decide whether to consider this use.
+          if (!GetFuzzerContext()->ChoosePercentage(
+                  GetFuzzerContext()->GetChanceOfReplacingIrrelevantId())) {
+            return;
+          }
+
+          // The id must be used as an input operand.
+          if (use_index < use_inst->NumOperands() - use_inst->NumInOperands()) {
+            // The id is used as an output operand, so we cannot replace this
+            // usage.
+            return;
+          }
+
+          // Get the input operand index for this use, from the absolute operand
+          // index.
+          uint32_t in_index =
+              fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
+
+          // Only go ahead if this id use can be replaced in principle.
+          if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(),
+                                              *GetTransformationContext(),
+                                              use_inst, in_index)) {
+            return;
+          }
+
+          // Find out which ids could be used to replace this use.
+          std::vector<uint32_t> available_replacement_ids;
+
+          for (auto replacement_id : types_to_ids[type_id]) {
+            // It would be pointless to replace an id with itself.
+            if (replacement_id == irrelevant_id) {
+              continue;
+            }
+
+            // We cannot replace a variable initializer with a non-constant.
+            if (TransformationReplaceIrrelevantId::
+                    AttemptsToReplaceVariableInitializerWithNonConstant(
+                        *use_inst, *GetIRContext()->get_def_use_mgr()->GetDef(
+                                       replacement_id))) {
+              continue;
+            }
+
+            // Only consider this replacement if the use point is within a basic
+            // block and the id is available at the use point.
+            //
+            // There might be opportunities for replacing a non-block use of an
+            // irrelevant id - such as the initializer of a global variable -
+            // with another id, but it would require some care (e.g. to ensure
+            // that the replacement id is defined earlier) and does not seem
+            // worth doing.
+            if (GetIRContext()->get_instr_block(use_inst) &&
+                fuzzerutil::IdIsAvailableAtUse(GetIRContext(), use_inst,
+                                               in_index, replacement_id)) {
+              available_replacement_ids.push_back(replacement_id);
+            }
+          }
+
+          // Only go ahead if there is at least one id with which this use can
+          // be replaced.
+          if (available_replacement_ids.empty()) {
+            return;
+          }
+
+          // Choose the replacement id randomly.
+          uint32_t replacement_id =
+              available_replacement_ids[GetFuzzerContext()->RandomIndex(
+                  available_replacement_ids)];
+
+          // Add this replacement to the list of transformations to apply.
+          transformations_to_apply.emplace_back(
+              TransformationReplaceIrrelevantId(
+                  MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
+                                             in_index),
+                  replacement_id));
+        });
+  }
+
+  // Apply all the transformations.
+  for (const auto& transformation : transformations_to_apply) {
+    ApplyTransformation(transformation);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h
new file mode 100644
index 0000000..ab3f01d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_irrelevant_ids.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPLACE_IRRELEVANT_IDS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that, for every use of an irrelevant id, checks if it is
+// possible to replace it with other ids of the same type and randomly decides
+// whether to do it.
+class FuzzerPassReplaceIrrelevantIds : public FuzzerPass {
+ public:
+  FuzzerPassReplaceIrrelevantIds(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceIrrelevantIds();
+
+  void Apply() override;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_IRRELEVANT_IDS_H_
diff --git a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp
index 1e5d697..c3e6578 100644
--- a/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp
+++ b/source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.cpp
@@ -34,34 +34,29 @@
     ~FuzzerPassReplaceLinearAlgebraInstructions() = default;
 
 void FuzzerPassReplaceLinearAlgebraInstructions::Apply() {
-  // For each instruction, checks whether it is a supported linear algebra
-  // instruction. In this case, the transformation is randomly applied.
-  GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
-    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354):
-    // Right now we only support certain operations. When this issue is
-    // addressed the following conditional can use the function
-    // |spvOpcodeIsLinearAlgebra|.
-    if (instruction->opcode() != SpvOpVectorTimesScalar &&
-        instruction->opcode() != SpvOpMatrixTimesScalar &&
-        instruction->opcode() != SpvOpVectorTimesMatrix &&
-        instruction->opcode() != SpvOpMatrixTimesVector &&
-        instruction->opcode() != SpvOpMatrixTimesMatrix &&
-        instruction->opcode() != SpvOpDot) {
-      return;
-    }
+  // For each instruction, checks whether it is a linear algebra instruction. In
+  // this case, the transformation is randomly applied.
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        if (!spvOpcodeIsLinearAlgebra(instruction.opcode())) {
+          continue;
+        }
 
-    if (!GetFuzzerContext()->ChoosePercentage(
-            GetFuzzerContext()
-                ->GetChanceOfReplacingLinearAlgebraInstructions())) {
-      return;
-    }
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()
+                    ->GetChanceOfReplacingLinearAlgebraInstructions())) {
+          continue;
+        }
 
-    ApplyTransformation(TransformationReplaceLinearAlgebraInstruction(
-        GetFuzzerContext()->GetFreshIds(
-            TransformationReplaceLinearAlgebraInstruction::
-                GetRequiredFreshIdCount(GetIRContext(), instruction)),
-        MakeInstructionDescriptor(GetIRContext(), instruction)));
-  });
+        ApplyTransformation(TransformationReplaceLinearAlgebraInstruction(
+            GetFuzzerContext()->GetFreshIds(
+                TransformationReplaceLinearAlgebraInstruction::
+                    GetRequiredFreshIdCount(GetIRContext(), &instruction)),
+            MakeInstructionDescriptor(GetIRContext(), &instruction)));
+      }
+    }
+  }
 }
 
 }  // namespace fuzz
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 db09500..67871db 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
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H
-#define SPIRV_TOOLS_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H
+#ifndef SOURCE_FUZZ_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -38,4 +38,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_LOADS_STORES_WITH_COPY_MEMORIES_H_
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
new file mode 100644
index 0000000..433cf74
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
@@ -0,0 +1,121 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_replace_opphi_ids_from_dead_predecessors.h"
+
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::
+    FuzzerPassReplaceOpPhiIdsFromDeadPredecessors(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::
+    ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors() = default;
+
+void FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::Apply() {
+  // Keep a vector of the transformations to apply.
+  std::vector<TransformationReplaceOpPhiIdFromDeadPredecessor> transformations;
+
+  // Loop through the reachable blocks in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    GetIRContext()->cfg()->ForEachBlockInPostOrder(
+        &*function.begin(),
+        [this, &function, &transformations](opt::BasicBlock* block) {
+          // Only consider dead blocks.
+          if (!GetTransformationContext()->GetFactManager()->BlockIsDead(
+                  block->id())) {
+            return;
+          }
+
+          // Find all the uses of the label id of the block inside OpPhi
+          // instructions.
+          GetIRContext()->get_def_use_mgr()->ForEachUse(
+              block->id(), [this, &function, block, &transformations](
+                               opt::Instruction* instruction, uint32_t) {
+                // Only consider OpPhi instructions.
+                if (instruction->opcode() != SpvOpPhi) {
+                  return;
+                }
+
+                // Randomly decide whether to consider this use.
+                if (!GetFuzzerContext()->ChoosePercentage(
+                        GetFuzzerContext()
+                            ->GetChanceOfReplacingOpPhiIdFromDeadPredecessor())) {
+                  return;
+                }
+
+                // Get the current id corresponding to the predecessor.
+                uint32_t current_id = 0;
+                for (uint32_t i = 1; i < instruction->NumInOperands(); i += 2) {
+                  if (instruction->GetSingleWordInOperand(i) == block->id()) {
+                    // The corresponding id is at the index of the block - 1.
+                    current_id = instruction->GetSingleWordInOperand(i - 1);
+                    break;
+                  }
+                }
+                assert(
+                    current_id != 0 &&
+                    "The predecessor - and corresponding id - should always be "
+                    "found.");
+
+                uint32_t type_id = instruction->type_id();
+
+                // Find all the suitable instructions to replace the id.
+                const auto& candidates = FindAvailableInstructions(
+                    &function, block, block->end(),
+                    [type_id, current_id](opt::IRContext* /* unused */,
+                                          opt::Instruction* candidate) -> bool {
+                      // Only consider instructions with a result id different
+                      // from the currently-used one, and with the right type.
+                      return candidate->HasResultId() &&
+                             candidate->type_id() == type_id &&
+                             candidate->result_id() != current_id;
+                    });
+
+                // If there is no possible replacement, we cannot apply any
+                // transformation.
+                if (candidates.empty()) {
+                  return;
+                }
+
+                // Choose one of the candidates.
+                uint32_t replacement_id =
+                    candidates[GetFuzzerContext()->RandomIndex(candidates)]
+                        ->result_id();
+
+                // Add a new transformation to the list of transformations to
+                // apply.
+                transformations.emplace_back(
+                    TransformationReplaceOpPhiIdFromDeadPredecessor(
+                        instruction->result_id(), block->id(), replacement_id));
+              });
+        });
+  }
+
+  // Apply all the transformations.
+  for (const auto& transformation : transformations) {
+    ApplyTransformation(transformation);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
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
new file mode 100644
index 0000000..972c5f9
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Replaces id operands in OpPhi instructions with other available ids of the
+// right type, where the corresponding predecessor is dead.
+class FuzzerPassReplaceOpPhiIdsFromDeadPredecessors : public FuzzerPass {
+ public:
+  FuzzerPassReplaceOpPhiIdsFromDeadPredecessors(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors();
+
+  void Apply() override;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_
diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp
new file mode 100644
index 0000000..c3db0ef
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp
@@ -0,0 +1,172 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_replace_opselects_with_conditional_branches.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
+#include "source/fuzz/transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassReplaceOpSelectsWithConditionalBranches::
+    FuzzerPassReplaceOpSelectsWithConditionalBranches(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceOpSelectsWithConditionalBranches::
+    ~FuzzerPassReplaceOpSelectsWithConditionalBranches() = default;
+
+void FuzzerPassReplaceOpSelectsWithConditionalBranches::Apply() {
+  // Keep track of the instructions that we want to replace. We need to collect
+  // them in a vector, since it's not safe to modify the module while iterating
+  // over it.
+  std::vector<uint32_t> replaceable_opselect_instruction_ids;
+
+  // Loop over all the instructions in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // We cannot split loop headers, so we don't need to consider instructions
+      // in loop headers that are also merge blocks (since they would need to be
+      // split).
+      if (block.IsLoopHeader() &&
+          GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(
+              block.id())) {
+        continue;
+      }
+
+      for (auto& instruction : block) {
+        // We only care about OpSelect instructions.
+        if (instruction.opcode() != SpvOpSelect) {
+          continue;
+        }
+
+        // Randomly choose whether to consider this instruction for replacement.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()
+                    ->GetChanceOfReplacingOpselectWithConditionalBranch())) {
+          continue;
+        }
+
+        // If the selector does not have scalar boolean type (i.e., it is a
+        // boolean vector) then ignore this OpSelect.
+        if (GetIRContext()
+                ->get_def_use_mgr()
+                ->GetDef(fuzzerutil::GetTypeId(
+                    GetIRContext(), instruction.GetSingleWordInOperand(0)))
+                ->opcode() != SpvOpTypeBool) {
+          continue;
+        }
+
+        // If the block is a loop header and we need to split it, the
+        // transformation cannot be applied because loop headers cannot be
+        // split. We can break out of this loop because the transformation can
+        // only be applied to at most the first instruction in a loop header.
+        if (block.IsLoopHeader() && InstructionNeedsSplitBefore(&instruction)) {
+          break;
+        }
+
+        // If the instruction separates an OpSampledImage from its use, the
+        // block cannot be split around it and the instruction cannot be
+        // replaced.
+        if (fuzzerutil::
+                SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+                    &block, &instruction)) {
+          continue;
+        }
+
+        // We can apply the transformation to this instruction.
+        replaceable_opselect_instruction_ids.push_back(instruction.result_id());
+      }
+    }
+  }
+
+  // Apply the transformations, splitting the blocks containing the
+  // instructions, if necessary.
+  for (uint32_t instruction_id : replaceable_opselect_instruction_ids) {
+    auto instruction =
+        GetIRContext()->get_def_use_mgr()->GetDef(instruction_id);
+
+    // If the instruction requires the block containing it to be split before
+    // it, split the block.
+    if (InstructionNeedsSplitBefore(instruction)) {
+      ApplyTransformation(TransformationSplitBlock(
+          MakeInstructionDescriptor(GetIRContext(), instruction),
+          GetFuzzerContext()->GetFreshId()));
+    }
+
+    // Decide whether to have two branches or just one.
+    bool two_branches = GetFuzzerContext()->ChoosePercentage(
+        GetFuzzerContext()
+            ->GetChanceOfAddingBothBranchesWhenReplacingOpSelect());
+
+    // If there will be only one branch, decide whether it will be the true
+    // branch or the false branch.
+    bool true_branch_id_zero =
+        !two_branches &&
+        GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()
+                ->GetChanceOfAddingTrueBranchWhenReplacingOpSelect());
+    bool false_branch_id_zero = !two_branches && !true_branch_id_zero;
+
+    uint32_t true_branch_id =
+        true_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId();
+    uint32_t false_branch_id =
+        false_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId();
+
+    ApplyTransformation(TransformationReplaceOpSelectWithConditionalBranch(
+        instruction_id, true_branch_id, false_branch_id));
+  }
+}
+
+bool FuzzerPassReplaceOpSelectsWithConditionalBranches::
+    InstructionNeedsSplitBefore(opt::Instruction* instruction) {
+  assert(instruction && instruction->opcode() == SpvOpSelect &&
+         "The instruction must be OpSelect.");
+
+  auto block = GetIRContext()->get_instr_block(instruction);
+  assert(block && "The instruction must be contained in a block.");
+
+  // We need to split the block if the instruction is not the first in its
+  // block.
+  if (instruction->unique_id() != block->begin()->unique_id()) {
+    return true;
+  }
+
+  // We need to split the block if it is a merge block.
+  if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
+    return true;
+  }
+
+  // We need to split the block if it has more than one predecessor.
+  if (GetIRContext()->cfg()->preds(block->id()).size() != 1) {
+    return true;
+  }
+
+  // We need to split the block if its predecessor is a header or it does not
+  // branch unconditionally to the block.
+  auto predecessor = GetIRContext()->get_instr_block(
+      GetIRContext()->cfg()->preds(block->id())[0]);
+  return predecessor->MergeBlockIdIfAny() ||
+         predecessor->terminator()->opcode() != SpvOpBranch;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h
new file mode 100644
index 0000000..04c6cc6
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass to replace OpSelect instructions (where the condition is a
+// scalar boolean) with conditional branches and OpPhi instructions.
+class FuzzerPassReplaceOpSelectsWithConditionalBranches : public FuzzerPass {
+ public:
+  FuzzerPassReplaceOpSelectsWithConditionalBranches(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceOpSelectsWithConditionalBranches() override;
+
+  void Apply() override;
+
+ private:
+  // Returns true if any of the following holds:
+  // - the instruction is not the first in its block
+  // - the block containing it is a merge block
+  // - the block does not have a unique predecessor
+  // - the predecessor of the block is the header of a construct
+  // - the predecessor does not branch unconditionally to the block
+  // If this function returns true, the block must be split before the
+  // instruction for TransformationReplaceOpSelectWithConditionalBranch to be
+  // applicable.
+  // Assumes that the instruction is OpSelect.
+  bool InstructionNeedsSplitBefore(opt::Instruction* instruction);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_H_
diff --git a/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp b/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp
index 8672a3b..6b3a63b 100644
--- a/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp
+++ b/source/fuzz/fuzzer_pass_replace_parameter_with_global.cpp
@@ -53,27 +53,23 @@
     // function has at least one parameter.
     if (std::none_of(params.begin(), params.end(),
                      [this](const opt::Instruction* param) {
-                       const auto* param_type =
-                           GetIRContext()->get_type_mgr()->GetType(
-                               param->type_id());
-                       assert(param_type && "Parameter has invalid type");
                        return TransformationReplaceParameterWithGlobal::
-                           IsParameterTypeSupported(*param_type);
+                           IsParameterTypeSupported(GetIRContext(),
+                                                    param->type_id());
                      })) {
       continue;
     }
 
     // Select id of a parameter to replace.
-    const opt::Instruction* replaced_param = nullptr;
-    const opt::analysis::Type* param_type = nullptr;
+    const opt::Instruction* replaced_param;
+    uint32_t param_type_id;
     do {
       replaced_param = GetFuzzerContext()->RemoveAtRandomIndex(&params);
-      param_type =
-          GetIRContext()->get_type_mgr()->GetType(replaced_param->type_id());
-      assert(param_type && "Parameter has invalid type");
+      param_type_id = replaced_param->type_id();
+      assert(param_type_id && "Parameter has invalid type");
     } while (
         !TransformationReplaceParameterWithGlobal::IsParameterTypeSupported(
-            *param_type));
+            GetIRContext(), param_type_id));
 
     assert(replaced_param && "Unable to find a parameter to replace");
 
diff --git a/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp b/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp
index e49eacb..0e0610f 100644
--- a/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp
+++ b/source/fuzz/fuzzer_pass_replace_params_with_struct.cpp
@@ -53,15 +53,13 @@
     std::iota(parameter_index.begin(), parameter_index.end(), 0);
 
     // Remove the indices of unsupported parameters.
-    auto new_end = std::remove_if(
-        parameter_index.begin(), parameter_index.end(),
-        [this, &params](uint32_t index) {
-          const auto* type =
-              GetIRContext()->get_type_mgr()->GetType(params[index]->type_id());
-          assert(type && "Parameter has invalid type");
-          return !TransformationReplaceParamsWithStruct::
-              IsParameterTypeSupported(*type);
-        });
+    auto new_end =
+        std::remove_if(parameter_index.begin(), parameter_index.end(),
+                       [this, &params](uint32_t index) {
+                         return !TransformationReplaceParamsWithStruct::
+                             IsParameterTypeSupported(GetIRContext(),
+                                                      params[index]->type_id());
+                       });
 
     // std::remove_if changes the vector so that removed elements are placed at
     // the end (i.e. [new_end, parameter_index.end()) is a range of removed
@@ -98,7 +96,7 @@
       parameter_id.push_back(params[index]->result_id());
     }
 
-    std::unordered_map<uint32_t, uint32_t> caller_id_to_fresh_id;
+    std::map<uint32_t, uint32_t> caller_id_to_fresh_id;
     for (const auto* inst :
          fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
       caller_id_to_fresh_id[inst->result_id()] =
diff --git a/source/fuzz/fuzzer_pass_split_blocks.h b/source/fuzz/fuzzer_pass_split_blocks.h
index 278ec6d..0ece48a 100644
--- a/source/fuzz/fuzzer_pass_split_blocks.h
+++ b/source/fuzz/fuzzer_pass_split_blocks.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
-#define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
+#ifndef SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_H_
 
 #include "source/fuzz/fuzzer_pass.h"
 
@@ -37,4 +37,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_
+#endif  // SOURCE_FUZZ_FUZZER_PASS_SPLIT_BLOCKS_H_
diff --git a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp
index dc8b1eb..9433a61 100644
--- a/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp
+++ b/source/fuzz/fuzzer_pass_swap_conditional_branch_operands.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/fuzz/fuzzer_pass_swap_conditional_branch_operands.h"
+
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
diff --git a/source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp
new file mode 100644
index 0000000..e6cdca4
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.cpp
@@ -0,0 +1,140 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_regions_in_selections.h"
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_split_block.h"
+#include "source/fuzz/transformation_wrap_region_in_selection.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassWrapRegionsInSelections::FuzzerPassWrapRegionsInSelections(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassWrapRegionsInSelections::~FuzzerPassWrapRegionsInSelections() =
+    default;
+
+void FuzzerPassWrapRegionsInSelections::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfWrappingRegionInSelection())) {
+      continue;
+    }
+
+    // It is easier to select an element at random from a vector than from an
+    // instruction list.
+    std::vector<opt::BasicBlock*> header_block_candidates;
+    for (auto& block : function) {
+      header_block_candidates.push_back(&block);
+    }
+
+    if (header_block_candidates.empty()) {
+      continue;
+    }
+
+    // Try to get a header block candidate that will increase the chances of the
+    // transformation being applicable.
+    auto* header_block_candidate = MaybeGetHeaderBlockCandidate(
+        header_block_candidates[GetFuzzerContext()->RandomIndex(
+            header_block_candidates)]);
+    if (!header_block_candidate) {
+      continue;
+    }
+
+    std::vector<opt::BasicBlock*> merge_block_candidates;
+    for (auto& block : function) {
+      if (GetIRContext()->GetDominatorAnalysis(&function)->StrictlyDominates(
+              header_block_candidate, &block) &&
+          GetIRContext()
+              ->GetPostDominatorAnalysis(&function)
+              ->StrictlyDominates(&block, header_block_candidate)) {
+        merge_block_candidates.push_back(&block);
+      }
+    }
+
+    if (merge_block_candidates.empty()) {
+      continue;
+    }
+
+    // Try to get a merge block candidate that will increase the chances of the
+    // transformation being applicable.
+    auto* merge_block_candidate = MaybeGetMergeBlockCandidate(
+        merge_block_candidates[GetFuzzerContext()->RandomIndex(
+            merge_block_candidates)]);
+    if (!merge_block_candidate) {
+      continue;
+    }
+
+    if (!TransformationWrapRegionInSelection::IsApplicableToBlockRange(
+            GetIRContext(), header_block_candidate->id(),
+            merge_block_candidate->id())) {
+      continue;
+    }
+
+    // This boolean constant will be used as a condition for the
+    // OpBranchConditional instruction. We mark it as irrelevant to be able to
+    // replace it with a more interesting value later.
+    auto branch_condition = GetFuzzerContext()->ChooseEven();
+    FindOrCreateBoolConstant(branch_condition, true);
+
+    ApplyTransformation(TransformationWrapRegionInSelection(
+        header_block_candidate->id(), merge_block_candidate->id(),
+        branch_condition));
+  }
+}
+
+opt::BasicBlock*
+FuzzerPassWrapRegionsInSelections::MaybeGetHeaderBlockCandidate(
+    opt::BasicBlock* header_block_candidate) {
+  // Try to create a preheader if |header_block_candidate| is a loop header.
+  if (header_block_candidate->IsLoopHeader()) {
+    // GetOrCreateSimpleLoopPreheader only supports reachable blocks.
+    return GetIRContext()->cfg()->preds(header_block_candidate->id()).size() ==
+                   1
+               ? nullptr
+               : GetOrCreateSimpleLoopPreheader(header_block_candidate->id());
+  }
+
+  // Try to split |header_block_candidate| if it's already a header block.
+  if (header_block_candidate->GetMergeInst()) {
+    SplitBlockAfterOpPhiOrOpVariable(header_block_candidate->id());
+  }
+
+  return header_block_candidate;
+}
+
+opt::BasicBlock* FuzzerPassWrapRegionsInSelections::MaybeGetMergeBlockCandidate(
+    opt::BasicBlock* merge_block_candidate) {
+  // If |merge_block_candidate| is a merge block of some construct, try to split
+  // it and return a newly created block.
+  if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(
+          merge_block_candidate->id())) {
+    // We can't split a merge block if it's also a loop header.
+    return merge_block_candidate->IsLoopHeader()
+               ? nullptr
+               : SplitBlockAfterOpPhiOrOpVariable(merge_block_candidate->id());
+  }
+
+  return merge_block_candidate;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_wrap_regions_in_selections.h b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.h
new file mode 100644
index 0000000..eb28d20
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_wrap_regions_in_selections.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REGIONS_IN_SELECTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_WRAP_REGIONS_IN_SELECTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly wraps a region of blocks in every function into a selection
+// construct.
+class FuzzerPassWrapRegionsInSelections : public FuzzerPass {
+ public:
+  FuzzerPassWrapRegionsInSelections(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassWrapRegionsInSelections() override;
+
+  void Apply() override;
+
+ private:
+  // Tries to adjust |header_block_candidate| such that
+  // TransformationWrapRegionInSelection has higher chances of being
+  // applied. In particular, tries to split |header_block_candidate| if it's
+  // already a header block of some other construct.
+  opt::BasicBlock* MaybeGetHeaderBlockCandidate(
+      opt::BasicBlock* header_block_candidate);
+
+  // Tries to adjust |merge_block_candidate| such that
+  // TransformationWrapRegionInSelection has higher chances of being
+  // applied. In particular, tries to split |merge_block_candidate| if it's
+  // already a merge block of some other construct.
+  opt::BasicBlock* MaybeGetMergeBlockCandidate(
+      opt::BasicBlock* merge_block_candidate);
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_WRAP_REGIONS_IN_SELECTIONS_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 16cbf1a..8c14db4 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -43,6 +43,10 @@
 
 }  // namespace
 
+const spvtools::MessageConsumer kSilentMessageConsumer =
+    [](spv_message_level_t, const char*, const spv_position_t&,
+       const char*) -> void {};
+
 bool IsFreshId(opt::IRContext* context, uint32_t id) {
   return !context->get_def_use_mgr()->GetDef(id);
 }
@@ -271,6 +275,11 @@
     return false;
   }
   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()) {
       case SpvOpConstantNull:
@@ -395,13 +404,66 @@
   }
 }
 
-bool IsValid(opt::IRContext* context, spv_validator_options validator_options) {
+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);
   return tools.Validate(binary.data(), binary.size(), validator_options);
 }
 
+bool IsValidAndWellFormed(const opt::IRContext* ir_context,
+                          spv_validator_options validator_options,
+                          MessageConsumer consumer) {
+  if (!IsValid(ir_context, validator_options, consumer)) {
+    // Expression to dump |ir_context| to /data/temp/shader.spv:
+    //    DumpShader(ir_context, "/data/temp/shader.spv")
+    consumer(SPV_MSG_INFO, nullptr, {},
+             "Module is invalid (set a breakpoint to inspect).");
+    return false;
+  }
+  // Check that all blocks in the module have appropriate parent functions.
+  for (auto& function : *ir_context->module()) {
+    for (auto& block : function) {
+      if (block.GetParent() == nullptr) {
+        std::stringstream ss;
+        ss << "Block " << block.id() << " has no parent; its parent should be "
+           << function.result_id() << " (set a breakpoint to inspect).";
+        consumer(SPV_MSG_INFO, nullptr, {}, ss.str().c_str());
+        return false;
+      }
+      if (block.GetParent() != &function) {
+        std::stringstream ss;
+        ss << "Block " << block.id() << " should have parent "
+           << function.result_id() << " but instead has parent "
+           << block.GetParent() << " (set a breakpoint to inspect).";
+        consumer(SPV_MSG_INFO, nullptr, {}, ss.str().c_str());
+        return false;
+      }
+    }
+  }
+
+  // Check that all instructions have distinct unique ids.  We map each unique
+  // id to the first instruction it is observed to be associated with so that
+  // if we encounter a duplicate we have access to the previous instruction -
+  // 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,
+                                     &unique_ids](opt::Instruction* inst) {
+    if (unique_ids.count(inst->unique_id()) != 0) {
+      consumer(SPV_MSG_INFO, nullptr, {},
+               "Two instructions have the same unique id (set a breakpoint to "
+               "inspect).");
+      found_duplicate = true;
+    }
+    unique_ids.insert({inst->unique_id(), inst});
+  });
+  return !found_duplicate;
+}
+
 std::unique_ptr<opt::IRContext> CloneIRContext(opt::IRContext* context) {
   std::vector<uint32_t> binary;
   context->module()->ToBinary(&binary, false);
@@ -432,6 +494,28 @@
   return result;
 }
 
+uint32_t GetLoopFromMergeBlock(opt::IRContext* ir_context,
+                               uint32_t merge_block_id) {
+  uint32_t result = 0;
+  ir_context->get_def_use_mgr()->WhileEachUse(
+      merge_block_id,
+      [ir_context, &result](opt::Instruction* use_instruction,
+                            uint32_t use_index) -> bool {
+        switch (use_instruction->opcode()) {
+          case SpvOpLoopMerge:
+            // The merge block operand is the first operand in OpLoopMerge.
+            if (use_index == 0) {
+              result = ir_context->get_instr_block(use_instruction)->id();
+              return false;
+            }
+            return true;
+          default:
+            return true;
+        }
+      });
+  return result;
+}
+
 uint32_t FindFunctionType(opt::IRContext* ir_context,
                           const std::vector<uint32_t>& type_ids) {
   // Look through the existing types for a match.
@@ -476,6 +560,16 @@
   return nullptr;
 }
 
+bool FunctionContainsOpKillOrUnreachable(const opt::Function& function) {
+  for (auto& block : function) {
+    if (block.terminator()->opcode() == SpvOpKill ||
+        block.terminator()->opcode() == SpvOpUnreachable) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) {
   for (auto& entry_point : context->module()->entry_points()) {
     if (entry_point.GetSingleWordInOperand(1) == function_id) {
@@ -488,6 +582,9 @@
 bool IdIsAvailableAtUse(opt::IRContext* context,
                         opt::Instruction* use_instruction,
                         uint32_t use_input_operand_index, uint32_t id) {
+  assert(context->get_instr_block(use_instruction) &&
+         "|use_instruction| must be in a basic block");
+
   auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
   auto enclosing_function =
       context->get_instr_block(use_instruction)->GetParent();
@@ -506,6 +603,12 @@
     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))) {
+    // Skip unreachable blocks.
+    return false;
+  }
   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
@@ -521,24 +624,48 @@
 bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
                                     opt::Instruction* instruction,
                                     uint32_t id) {
-  auto defining_instruction = context->get_def_use_mgr()->GetDef(id);
-  auto enclosing_function = context->get_instr_block(instruction)->GetParent();
+  assert(context->get_instr_block(instruction) &&
+         "|instruction| must be in a basic block");
+
+  auto id_definition = context->get_def_use_mgr()->GetDef(id);
+  auto function_enclosing_instruction =
+      context->get_instr_block(instruction)->GetParent();
   // If the id a function parameter, it needs to be associated with the
   // function containing the instruction.
-  if (defining_instruction->opcode() == SpvOpFunctionParameter) {
-    return InstructionIsFunctionParameter(defining_instruction,
-                                          enclosing_function);
+  if (id_definition->opcode() == SpvOpFunctionParameter) {
+    return InstructionIsFunctionParameter(id_definition,
+                                          function_enclosing_instruction);
   }
   if (!context->get_instr_block(id)) {
     // The id is at global scope.
     return true;
   }
-  if (defining_instruction == instruction) {
+  if (id_definition == instruction) {
     // The instruction is not available right before its own definition.
     return false;
   }
-  return context->GetDominatorAnalysis(enclosing_function)
-      ->Dominates(defining_instruction, instruction);
+  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)) &&
+      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
+    // the instruction.
+    return true;
+  }
+  if (id_definition->opcode() == SpvOpVariable &&
+      function_enclosing_instruction ==
+          context->get_instr_block(id)->GetParent()) {
+    assert(!dominator_analysis->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|.
+    // This is OK despite |instruction| being unreachable.
+    return true;
+  }
+  return false;
 }
 
 bool InstructionIsFunctionParameter(opt::Instruction* instruction,
@@ -557,7 +684,9 @@
 }
 
 uint32_t GetTypeId(opt::IRContext* context, uint32_t result_id) {
-  return context->get_def_use_mgr()->GetDef(result_id)->type_id();
+  const auto* inst = context->get_def_use_mgr()->GetDef(result_id);
+  assert(inst && "|result_id| is invalid");
+  return inst->type_id();
 }
 
 uint32_t GetPointeeTypeIdFromPointerType(opt::Instruction* pointer_type_inst) {
@@ -757,6 +886,20 @@
   return result;
 }
 
+void RemoveParameter(opt::IRContext* ir_context, uint32_t parameter_id) {
+  auto* function = GetFunctionFromParameterId(ir_context, parameter_id);
+  assert(function && "|parameter_id| is invalid");
+  assert(!FunctionIsEntryPoint(ir_context, function->result_id()) &&
+         "Can't remove parameter from an entry point function");
+
+  function->RemoveParameter(parameter_id);
+
+  // We've just removed parameters from the function and cleared their memory.
+  // Make sure analyses have no dangling pointers.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
 std::vector<opt::Instruction*> GetCallers(opt::IRContext* ir_context,
                                           uint32_t function_id) {
   assert(FindFunction(ir_context, function_id) &&
@@ -812,6 +955,12 @@
   operand_ids.insert(operand_ids.end(), parameter_type_ids.begin(),
                      parameter_type_ids.end());
 
+  // A trivial case - we change nothing.
+  if (FindFunctionType(ir_context, operand_ids) ==
+      old_function_type->result_id()) {
+    return old_function_type->result_id();
+  }
+
   if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1 &&
       FindFunctionType(ir_context, operand_ids) == 0) {
     // We can change |old_function_type| only if it's used once in the module
@@ -835,17 +984,16 @@
     // existing one or create a new one.
     auto type_id = FindOrCreateFunctionType(
         ir_context, new_function_type_result_id, operand_ids);
+    assert(type_id != old_function_type->result_id() &&
+           "We should've handled this case above");
 
-    if (type_id != old_function_type->result_id()) {
-      function->DefInst().SetInOperand(1, {type_id});
+    function->DefInst().SetInOperand(1, {type_id});
 
-      // DefUseManager hasn't been updated yet, so if the following condition is
-      // true, then |old_function_type| will have no users when this function
-      // returns. We might as well remove it.
-      if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) {
-        old_function_type->RemoveFromList();
-        delete old_function_type;
-      }
+    // DefUseManager hasn't been updated yet, so if the following condition is
+    // true, then |old_function_type| will have no users when this function
+    // returns. We might as well remove it.
+    if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) {
+      ir_context->KillInst(old_function_type);
     }
 
     return type_id;
@@ -923,17 +1071,28 @@
 
 uint32_t MaybeGetStructType(opt::IRContext* ir_context,
                             const std::vector<uint32_t>& component_type_ids) {
-  std::vector<const opt::analysis::Type*> component_types;
-  component_types.reserve(component_type_ids.size());
-
-  for (auto type_id : component_type_ids) {
-    const auto* component_type = ir_context->get_type_mgr()->GetType(type_id);
-    assert(component_type && !component_type->AsFunction() &&
-           "Component type is invalid");
-    component_types.push_back(component_type);
+  for (auto& type_or_value : ir_context->types_values()) {
+    if (type_or_value.opcode() != SpvOpTypeStruct ||
+        type_or_value.NumInOperands() !=
+            static_cast<uint32_t>(component_type_ids.size())) {
+      continue;
+    }
+    bool all_components_match = true;
+    for (uint32_t i = 0; i < component_type_ids.size(); i++) {
+      if (type_or_value.GetSingleWordInOperand(i) != component_type_ids[i]) {
+        all_components_match = false;
+        break;
+      }
+    }
+    if (all_components_match) {
+      return type_or_value.result_id();
+    }
   }
+  return 0;
+}
 
-  opt::analysis::Struct type(component_types);
+uint32_t MaybeGetVoidType(opt::IRContext* ir_context) {
+  opt::analysis::Void type;
   return ir_context->get_type_mgr()->GetId(&type);
 }
 
@@ -941,35 +1100,34 @@
     opt::IRContext* ir_context,
     const TransformationContext& transformation_context,
     uint32_t scalar_or_composite_type_id, bool is_irrelevant) {
-  const auto* type =
-      ir_context->get_type_mgr()->GetType(scalar_or_composite_type_id);
-  assert(type && "|scalar_or_composite_type_id| is invalid");
+  const auto* type_inst =
+      ir_context->get_def_use_mgr()->GetDef(scalar_or_composite_type_id);
+  assert(type_inst && "|scalar_or_composite_type_id| is invalid");
 
-  switch (type->kind()) {
-    case opt::analysis::Type::kBool:
+  switch (type_inst->opcode()) {
+    case SpvOpTypeBool:
       return MaybeGetBoolConstant(ir_context, transformation_context, false,
                                   is_irrelevant);
-    case opt::analysis::Type::kFloat:
-    case opt::analysis::Type::kInteger: {
+    case SpvOpTypeFloat:
+    case SpvOpTypeInt: {
+      const auto width = type_inst->GetSingleWordInOperand(0);
       std::vector<uint32_t> words = {0};
-      if ((type->AsInteger() && type->AsInteger()->width() > 32) ||
-          (type->AsFloat() && type->AsFloat()->width() > 32)) {
+      if (width > 32) {
         words.push_back(0);
       }
 
       return MaybeGetScalarConstant(ir_context, transformation_context, words,
                                     scalar_or_composite_type_id, is_irrelevant);
     }
-    case opt::analysis::Type::kStruct: {
+    case SpvOpTypeStruct: {
       std::vector<uint32_t> component_ids;
-      for (const auto* component_type : type->AsStruct()->element_types()) {
-        auto component_type_id =
-            ir_context->get_type_mgr()->GetId(component_type);
-        assert(component_type_id && "Component type is invalid");
+      for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
+        const auto component_type_id = type_inst->GetSingleWordInOperand(i);
 
         auto component_id =
             MaybeGetZeroConstant(ir_context, transformation_context,
                                  component_type_id, is_irrelevant);
+
         if (component_id == 0 && is_irrelevant) {
           // Irrelevant constants can use either relevant or irrelevant
           // constituents.
@@ -988,14 +1146,9 @@
           ir_context, transformation_context, component_ids,
           scalar_or_composite_type_id, is_irrelevant);
     }
-    case opt::analysis::Type::kMatrix:
-    case opt::analysis::Type::kVector: {
-      const auto* component_type = type->AsVector()
-                                       ? type->AsVector()->element_type()
-                                       : type->AsMatrix()->element_type();
-      auto component_type_id =
-          ir_context->get_type_mgr()->GetId(component_type);
-      assert(component_type_id && "Component type is invalid");
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector: {
+      const auto component_type_id = type_inst->GetSingleWordInOperand(0);
 
       auto component_id = MaybeGetZeroConstant(
           ir_context, transformation_context, component_type_id, is_irrelevant);
@@ -1011,23 +1164,21 @@
         return 0;
       }
 
-      auto component_count = type->AsVector()
-                                 ? type->AsVector()->element_count()
-                                 : type->AsMatrix()->element_count();
+      const auto component_count = type_inst->GetSingleWordInOperand(1);
       return MaybeGetCompositeConstant(
           ir_context, transformation_context,
           std::vector<uint32_t>(component_count, component_id),
           scalar_or_composite_type_id, is_irrelevant);
     }
-    case opt::analysis::Type::kArray: {
-      auto component_type_id =
-          ir_context->get_type_mgr()->GetId(type->AsArray()->element_type());
-      assert(component_type_id && "Component type is invalid");
+    case SpvOpTypeArray: {
+      const auto component_type_id = type_inst->GetSingleWordInOperand(0);
 
       auto component_id = MaybeGetZeroConstant(
           ir_context, transformation_context, component_type_id, is_irrelevant);
 
       if (component_id == 0 && is_irrelevant) {
+        // Irrelevant constants can use either relevant or irrelevant
+        // constituents.
         component_id = MaybeGetZeroConstant(ir_context, transformation_context,
                                             component_type_id, false);
       }
@@ -1036,12 +1187,6 @@
         return 0;
       }
 
-      auto type_id = ir_context->get_type_mgr()->GetId(type);
-      assert(type_id && "|type| is invalid");
-
-      const auto* type_inst = ir_context->get_def_use_mgr()->GetDef(type_id);
-      assert(type_inst && "Array's type id is invalid");
-
       return MaybeGetCompositeConstant(
           ir_context, transformation_context,
           std::vector<uint32_t>(GetArraySize(*type_inst, ir_context),
@@ -1054,6 +1199,37 @@
   }
 }
 
+bool CanCreateConstant(opt::IRContext* ir_context, uint32_t type_id) {
+  opt::Instruction* type_instr = ir_context->get_def_use_mgr()->GetDef(type_id);
+  assert(type_instr != nullptr && "The type must exist.");
+  assert(spvOpcodeGeneratesType(type_instr->opcode()) &&
+         "A type-generating opcode was expected.");
+  switch (type_instr->opcode()) {
+    case SpvOpTypeBool:
+    case SpvOpTypeInt:
+    case SpvOpTypeFloat:
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector:
+      return true;
+    case SpvOpTypeArray:
+      return CanCreateConstant(ir_context,
+                               type_instr->GetSingleWordInOperand(0));
+    case SpvOpTypeStruct:
+      if (HasBlockOrBufferBlockDecoration(ir_context, type_id)) {
+        return false;
+      }
+      for (uint32_t index = 0; index < type_instr->NumInOperands(); index++) {
+        if (!CanCreateConstant(ir_context,
+                               type_instr->GetSingleWordInOperand(index))) {
+          return false;
+        }
+      }
+      return true;
+    default:
+      return false;
+  }
+}
+
 uint32_t MaybeGetScalarConstant(
     opt::IRContext* ir_context,
     const TransformationContext& transformation_context,
@@ -1084,10 +1260,7 @@
     bool is_irrelevant) {
   const auto* type = ir_context->get_type_mgr()->GetType(composite_type_id);
   (void)type;  // Make compilers happy in release mode.
-  assert(type &&
-         (type->AsArray() || type->AsStruct() || type->AsVector() ||
-          type->AsMatrix()) &&
-         "|composite_type_id| is invalid");
+  assert(IsCompositeType(type) && "|composite_type_id| is invalid");
 
   for (const auto& inst : ir_context->types_values()) {
     if (inst.opcode() == SpvOpConstantComposite &&
@@ -1232,6 +1405,15 @@
     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}});
   }
 
@@ -1241,6 +1423,30 @@
   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");
+
+  // Sign-extend or zero-extend the last |width| bits of |value|, depending on
+  // |is_signed|.
+  if (is_signed) {
+    // Sign-extend by shifting left and then shifting right, interpreting the
+    // integer as signed.
+    value = static_cast<int64_t>(value << (64 - width)) >> (64 - width);
+  } else {
+    // Zero-extend by shifting left and then shifting right, interpreting the
+    // integer as unsigned.
+    value = (value << (64 - width)) >> (64 - width);
+  }
+
+  std::vector<uint32_t> result;
+  result.push_back(static_cast<uint32_t>(value));
+  if (width > 32) {
+    result.push_back(static_cast<uint32_t>(value >> 32));
+  }
+  return result;
+}
+
 bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id,
                            uint32_t type2_id) {
   if (type1_id == type2_id) {
@@ -1273,7 +1479,354 @@
   return false;
 }
 
-}  // namespace fuzzerutil
+std::map<uint32_t, uint32_t> RepeatedUInt32PairToMap(
+    const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>& data) {
+  std::map<uint32_t, uint32_t> result;
 
+  for (const auto& entry : data) {
+    result[entry.first()] = entry.second();
+  }
+
+  return result;
+}
+
+google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>
+MapToRepeatedUInt32Pair(const std::map<uint32_t, uint32_t>& data) {
+  google::protobuf::RepeatedPtrField<protobufs::UInt32Pair> result;
+
+  for (const auto& entry : data) {
+    protobufs::UInt32Pair pair;
+    pair.set_first(entry.first);
+    pair.set_second(entry.second);
+    *result.Add() = std::move(pair);
+  }
+
+  return result;
+}
+
+opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
+                                                 uint32_t block_id,
+                                                 SpvOp opcode) {
+  // CFG::block uses std::map::at which throws an exception when |block_id| is
+  // invalid. The error message is unhelpful, though. Thus, we test that
+  // |block_id| is valid here.
+  const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id);
+  (void)label_inst;  // Make compilers happy in release mode.
+  assert(label_inst && label_inst->opcode() == SpvOpLabel &&
+         "|block_id| is invalid");
+
+  auto* block = ir_context->cfg()->block(block_id);
+  auto it = block->rbegin();
+  assert(it != block->rend() && "Basic block can't be empty");
+
+  if (block->GetMergeInst()) {
+    ++it;
+    assert(it != block->rend() &&
+           "|block| must have at least two instructions:"
+           "terminator and a merge instruction");
+  }
+
+  return CanInsertOpcodeBeforeInstruction(opcode, &*it) ? &*it : nullptr;
+}
+
+bool IdUseCanBeReplaced(opt::IRContext* ir_context,
+                        const TransformationContext& transformation_context,
+                        opt::Instruction* use_instruction,
+                        uint32_t use_in_operand_index) {
+  if (spvOpcodeIsAccessChain(use_instruction->opcode()) &&
+      use_in_operand_index > 0) {
+    // A replacement for an irrelevant index in OpAccessChain must be clamped
+    // first.
+    if (transformation_context.GetFactManager()->IdIsIrrelevant(
+            use_instruction->GetSingleWordInOperand(use_in_operand_index))) {
+      return false;
+    }
+
+    // This is an access chain index.  If the (sub-)object being accessed by the
+    // given index has struct type then we cannot replace the use, as it needs
+    // to be an OpConstant.
+
+    // Get the top-level composite type that is being accessed.
+    auto object_being_accessed = ir_context->get_def_use_mgr()->GetDef(
+        use_instruction->GetSingleWordInOperand(0));
+    auto pointer_type =
+        ir_context->get_type_mgr()->GetType(object_being_accessed->type_id());
+    assert(pointer_type->AsPointer());
+    auto composite_type_being_accessed =
+        pointer_type->AsPointer()->pointee_type();
+
+    // Now walk the access chain, tracking the type of each sub-object of the
+    // composite that is traversed, until the index of interest is reached.
+    for (uint32_t index_in_operand = 1; index_in_operand < use_in_operand_index;
+         index_in_operand++) {
+      // For vectors, matrices and arrays, getting the type of the sub-object is
+      // trivial. For the struct case, the sub-object type is field-sensitive,
+      // and depends on the constant index that is used.
+      if (composite_type_being_accessed->AsVector()) {
+        composite_type_being_accessed =
+            composite_type_being_accessed->AsVector()->element_type();
+      } else if (composite_type_being_accessed->AsMatrix()) {
+        composite_type_being_accessed =
+            composite_type_being_accessed->AsMatrix()->element_type();
+      } else if (composite_type_being_accessed->AsArray()) {
+        composite_type_being_accessed =
+            composite_type_being_accessed->AsArray()->element_type();
+      } else if (composite_type_being_accessed->AsRuntimeArray()) {
+        composite_type_being_accessed =
+            composite_type_being_accessed->AsRuntimeArray()->element_type();
+      } else {
+        assert(composite_type_being_accessed->AsStruct());
+        auto constant_index_instruction = ir_context->get_def_use_mgr()->GetDef(
+            use_instruction->GetSingleWordInOperand(index_in_operand));
+        assert(constant_index_instruction->opcode() == SpvOpConstant);
+        uint32_t member_index =
+            constant_index_instruction->GetSingleWordInOperand(0);
+        composite_type_being_accessed =
+            composite_type_being_accessed->AsStruct()
+                ->element_types()[member_index];
+      }
+    }
+
+    // We have found the composite type being accessed by the index we are
+    // considering replacing. If it is a struct, then we cannot do the
+    // replacement as struct indices must be constants.
+    if (composite_type_being_accessed->AsStruct()) {
+      return false;
+    }
+  }
+
+  if (use_instruction->opcode() == SpvOpFunctionCall &&
+      use_in_operand_index > 0) {
+    // This is a function call argument.  It is not allowed to have pointer
+    // type.
+
+    // Get the definition of the function being called.
+    auto function = ir_context->get_def_use_mgr()->GetDef(
+        use_instruction->GetSingleWordInOperand(0));
+    // From the function definition, get the function type.
+    auto function_type = ir_context->get_def_use_mgr()->GetDef(
+        function->GetSingleWordInOperand(1));
+    // OpTypeFunction's 0-th input operand is the function return type, and the
+    // function argument types follow. Because the arguments to OpFunctionCall
+    // start from input operand 1, we can use |use_in_operand_index| to get the
+    // type associated with this function argument.
+    auto parameter_type = ir_context->get_type_mgr()->GetType(
+        function_type->GetSingleWordInOperand(use_in_operand_index));
+    if (parameter_type->AsPointer()) {
+      return false;
+    }
+  }
+
+  if (use_instruction->opcode() == SpvOpImageTexelPointer &&
+      use_in_operand_index == 2) {
+    // The OpImageTexelPointer instruction has a Sample parameter that in some
+    // situations must be an id for the value 0.  To guard against disrupting
+    // that requirement, we do not replace this argument to that instruction.
+    return false;
+  }
+
+  return true;
+}
+
+bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
+                                  uint32_t struct_type_id) {
+  const auto* type_inst = ir_context->get_def_use_mgr()->GetDef(struct_type_id);
+  assert(type_inst && type_inst->opcode() == SpvOpTypeStruct &&
+         "|struct_type_id| is not a result id of an OpTypeStruct");
+
+  uint32_t builtin_count = 0;
+  ir_context->get_def_use_mgr()->ForEachUser(
+      type_inst,
+      [struct_type_id, &builtin_count](const opt::Instruction* user) {
+        if (user->opcode() == SpvOpMemberDecorate &&
+            user->GetSingleWordInOperand(0) == struct_type_id &&
+            static_cast<SpvDecoration>(user->GetSingleWordInOperand(2)) ==
+                SpvDecorationBuiltIn) {
+          ++builtin_count;
+        }
+      });
+
+  assert((builtin_count == 0 || builtin_count == type_inst->NumInOperands()) &&
+         "The module is invalid: either none or all of the members of "
+         "|struct_type_id| may be builtin");
+
+  return builtin_count != 0;
+}
+
+bool HasBlockOrBufferBlockDecoration(opt::IRContext* ir_context, uint32_t id) {
+  for (auto decoration : {SpvDecorationBlock, SpvDecorationBufferBlock}) {
+    if (!ir_context->get_decoration_mgr()->WhileEachDecoration(
+            id, decoration, [](const opt::Instruction & /*unused*/) -> bool {
+              return false;
+            })) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+    opt::BasicBlock* block_to_split, opt::Instruction* split_before) {
+  std::set<uint32_t> sampled_image_result_ids;
+  bool before_split = true;
+
+  // Check all the instructions in the block to split.
+  for (auto& instruction : *block_to_split) {
+    if (&instruction == &*split_before) {
+      before_split = false;
+    }
+    if (before_split) {
+      // If the instruction comes before the split and its opcode is
+      // OpSampledImage, record its result id.
+      if (instruction.opcode() == SpvOpSampledImage) {
+        sampled_image_result_ids.insert(instruction.result_id());
+      }
+    } else {
+      // If the instruction comes after the split, check if ids
+      // corresponding to OpSampledImage instructions defined before the split
+      // are used, and return true if they are.
+      if (!instruction.WhileEachInId(
+              [&sampled_image_result_ids](uint32_t* id) -> bool {
+                return !sampled_image_result_ids.count(*id);
+              })) {
+        return true;
+      }
+    }
+  }
+
+  // No usage that would be separated from the definition has been found.
+  return false;
+}
+
+bool InstructionHasNoSideEffects(const opt::Instruction& instruction) {
+  switch (instruction.opcode()) {
+    case SpvOpUndef:
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpArrayLength:
+    case SpvOpVectorExtractDynamic:
+    case SpvOpVectorInsertDynamic:
+    case SpvOpVectorShuffle:
+    case SpvOpCompositeConstruct:
+    case SpvOpCompositeExtract:
+    case SpvOpCompositeInsert:
+    case SpvOpCopyObject:
+    case SpvOpTranspose:
+    case SpvOpConvertFToU:
+    case SpvOpConvertFToS:
+    case SpvOpConvertSToF:
+    case SpvOpConvertUToF:
+    case SpvOpUConvert:
+    case SpvOpSConvert:
+    case SpvOpFConvert:
+    case SpvOpQuantizeToF16:
+    case SpvOpSatConvertSToU:
+    case SpvOpSatConvertUToS:
+    case SpvOpBitcast:
+    case SpvOpSNegate:
+    case SpvOpFNegate:
+    case SpvOpIAdd:
+    case SpvOpFAdd:
+    case SpvOpISub:
+    case SpvOpFSub:
+    case SpvOpIMul:
+    case SpvOpFMul:
+    case SpvOpUDiv:
+    case SpvOpSDiv:
+    case SpvOpFDiv:
+    case SpvOpUMod:
+    case SpvOpSRem:
+    case SpvOpSMod:
+    case SpvOpFRem:
+    case SpvOpFMod:
+    case SpvOpVectorTimesScalar:
+    case SpvOpMatrixTimesScalar:
+    case SpvOpVectorTimesMatrix:
+    case SpvOpMatrixTimesVector:
+    case SpvOpMatrixTimesMatrix:
+    case SpvOpOuterProduct:
+    case SpvOpDot:
+    case SpvOpIAddCarry:
+    case SpvOpISubBorrow:
+    case SpvOpUMulExtended:
+    case SpvOpSMulExtended:
+    case SpvOpAny:
+    case SpvOpAll:
+    case SpvOpIsNan:
+    case SpvOpIsInf:
+    case SpvOpIsFinite:
+    case SpvOpIsNormal:
+    case SpvOpSignBitSet:
+    case SpvOpLessOrGreater:
+    case SpvOpOrdered:
+    case SpvOpUnordered:
+    case SpvOpLogicalEqual:
+    case SpvOpLogicalNotEqual:
+    case SpvOpLogicalOr:
+    case SpvOpLogicalAnd:
+    case SpvOpLogicalNot:
+    case SpvOpSelect:
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    case SpvOpUGreaterThan:
+    case SpvOpSGreaterThan:
+    case SpvOpUGreaterThanEqual:
+    case SpvOpSGreaterThanEqual:
+    case SpvOpULessThan:
+    case SpvOpSLessThan:
+    case SpvOpULessThanEqual:
+    case SpvOpSLessThanEqual:
+    case SpvOpFOrdEqual:
+    case SpvOpFUnordEqual:
+    case SpvOpFOrdNotEqual:
+    case SpvOpFUnordNotEqual:
+    case SpvOpFOrdLessThan:
+    case SpvOpFUnordLessThan:
+    case SpvOpFOrdGreaterThan:
+    case SpvOpFUnordGreaterThan:
+    case SpvOpFOrdLessThanEqual:
+    case SpvOpFUnordLessThanEqual:
+    case SpvOpFOrdGreaterThanEqual:
+    case SpvOpFUnordGreaterThanEqual:
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+    case SpvOpBitFieldInsert:
+    case SpvOpBitFieldSExtract:
+    case SpvOpBitFieldUExtract:
+    case SpvOpBitReverse:
+    case SpvOpBitCount:
+    case SpvOpCopyLogical:
+    case SpvOpPhi:
+    case SpvOpPtrEqual:
+    case SpvOpPtrNotEqual:
+      return true;
+    default:
+      return false;
+  }
+}
+
+std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
+                                            uint32_t function_id) {
+  auto function = ir_context->GetFunction(function_id);
+  assert(function && "The function |function_id| must exist.");
+
+  std::set<uint32_t> result;
+
+  ir_context->cfg()->ForEachBlockInPostOrder(function->entry().get(),
+                                             [&result](opt::BasicBlock* block) {
+                                               if (block->IsReturn()) {
+                                                 result.emplace(block->id());
+                                               }
+                                             });
+
+  return result;
+}
+
+}  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index af8ff16..4e6ec36 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -15,6 +15,8 @@
 #ifndef SOURCE_FUZZ_FUZZER_UTIL_H_
 #define SOURCE_FUZZ_FUZZER_UTIL_H_
 
+#include <iostream>
+#include <map>
 #include <vector>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
@@ -22,6 +24,7 @@
 #include "source/opt/basic_block.h"
 #include "source/opt/instruction.h"
 #include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
 namespace fuzz {
@@ -29,6 +32,9 @@
 // Provides types and global utility methods for use by the fuzzer
 namespace fuzzerutil {
 
+// A silent message consumer.
+extern const spvtools::MessageConsumer kSilentMessageConsumer;
+
 // Function type that produces a SPIR-V module.
 using ModuleSupplier = std::function<std::unique_ptr<opt::IRContext>()>;
 
@@ -149,8 +155,18 @@
                                    opt::IRContext* ir_context);
 
 // Returns true if and only if |context| is valid, according to the validator
-// instantiated with |validator_options|.
-bool IsValid(opt::IRContext* context, spv_validator_options validator_options);
+// instantiated with |validator_options|.  |consumer| is used for error
+// reporting.
+bool IsValid(const opt::IRContext* context,
+             spv_validator_options validator_options, MessageConsumer consumer);
+
+// Returns true if and only if IsValid(|context|, |validator_options|) holds,
+// and furthermore every basic block in |context| has its enclosing function as
+// its parent, and every instruction in |context| has a distinct unique id.
+// |consumer| is used for error reporting.
+bool IsValidAndWellFormed(const opt::IRContext* context,
+                          spv_validator_options validator_options,
+                          MessageConsumer consumer);
 
 // Returns a clone of |context|, by writing |context| to a binary and then
 // parsing it again.
@@ -163,6 +179,11 @@
 // Returns true if and only if |block_id| is a merge block or continue target
 bool IsMergeOrContinue(opt::IRContext* ir_context, uint32_t block_id);
 
+// Returns the id of the header of the loop corresponding to the given loop
+// merge block. Returns 0 if |merge_block_id| is not a loop merge block.
+uint32_t GetLoopFromMergeBlock(opt::IRContext* ir_context,
+                               uint32_t merge_block_id);
+
 // Returns the result id of an instruction of the form:
 //  %id = OpTypeFunction |type_ids|
 // or 0 if no such instruction exists.
@@ -178,18 +199,23 @@
 // function exists.
 opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
 
+// Returns true if |function| has a block that the termination instruction is
+// OpKill or OpUnreachable.
+bool FunctionContainsOpKillOrUnreachable(const opt::Function& function);
+
 // Returns |true| if one of entry points has function id |function_id|.
 bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id);
 
 // Checks whether |id| is available (according to dominance rules) at the use
 // point defined by input operand |use_input_operand_index| of
-// |use_instruction|.
+// |use_instruction|. |use_instruction| must be a in some basic block.
 bool IdIsAvailableAtUse(opt::IRContext* context,
                         opt::Instruction* use_instruction,
                         uint32_t use_input_operand_index, uint32_t id);
 
 // Checks whether |id| is available (according to dominance rules) at the
-// program point directly before |instruction|.
+// program point directly before |instruction|. |instruction| must be in some
+// basic block.
 bool IdIsAvailableBeforeInstruction(opt::IRContext* context,
                                     opt::Instruction* instruction, uint32_t id);
 
@@ -288,6 +314,15 @@
 std::vector<opt::Instruction*> GetParameters(opt::IRContext* ir_context,
                                              uint32_t function_id);
 
+// Removes an OpFunctionParameter instruction with result id |parameter_id|
+// from the its function. Parameter's function must not be an entry-point
+// function. The function must have a parameter with result id |parameter_id|.
+//
+// Prefer using this function to opt::Function::RemoveParameter since
+// this function also guarantees that |ir_context| has no invalid pointers
+// to the removed parameter.
+void RemoveParameter(opt::IRContext* ir_context, uint32_t parameter_id);
+
 // Returns all OpFunctionCall instructions that call a function with result id
 // |function_id|.
 std::vector<opt::Instruction*> GetCallers(opt::IRContext* ir_context,
@@ -306,6 +341,10 @@
 // more users, it is removed from the module. Returns the result id of the
 // OpTypeFunction instruction that is used as a type of the function with
 // |function_id|.
+//
+// CAUTION: When the old type of the function is removed from the module, its
+//          memory is deallocated. Be sure not to use any pointers to the old
+//          type when this function returns.
 uint32_t UpdateFunctionType(opt::IRContext* ir_context, uint32_t function_id,
                             uint32_t new_function_type_result_id,
                             uint32_t return_type_id,
@@ -349,12 +388,17 @@
 uint32_t MaybeGetVectorType(opt::IRContext* ir_context,
                             uint32_t component_type_id, uint32_t element_count);
 
-// Returns a result id of an OpTypeStruct instruction if present. Returns 0
+// Returns a result id of an OpTypeStruct instruction whose field types exactly
+// match |component_type_ids| if such an instruction is present. Returns 0
 // otherwise. |component_type_ids| may not contain a result id of an
 // OpTypeFunction.
 uint32_t MaybeGetStructType(opt::IRContext* ir_context,
                             const std::vector<uint32_t>& component_type_ids);
 
+// Returns a result id of an OpTypeVoid instruction if present. Returns 0
+// otherwise.
+uint32_t MaybeGetVoidType(opt::IRContext* ir_context);
+
 // Recursive definition is the following:
 // - if |scalar_or_composite_type_id| is a result id of a scalar type - returns
 //   a result id of the following constants (depending on the type): int -> 0,
@@ -370,6 +414,12 @@
     const TransformationContext& transformation_context,
     uint32_t scalar_or_composite_type_id, bool is_irrelevant);
 
+// Returns true if it is possible to create an OpConstant or an
+// OpConstantComposite instruction of type |type_id|. That is, returns true if
+// the type associated with |type_id| and all its constituents are either scalar
+// or composite.
+bool CanCreateConstant(opt::IRContext* ir_context, uint32_t type_id);
+
 // Returns the result id of an OpConstant instruction. |scalar_type_id| must be
 // a result id of a scalar type (i.e. int, float or bool). Returns 0 if no such
 // instruction is present in the module. The returned id either participates in
@@ -445,10 +495,22 @@
 
 // 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.
+// 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.
+// |width| must be <= 64.
+// If |width| <= 32, returns a vector containing one value. If |width| > 64,
+// returns a vector containing two values, with the first one representing the
+// lower-order word of the value and the second one representing the
+// higher-order word.
+std::vector<uint32_t> IntToWords(uint64_t value, uint32_t width,
+                                 bool is_signed);
+
 // Returns a bit pattern that represents a floating-point |value|.
 inline uint32_t FloatToWord(float value) {
   uint32_t result;
@@ -463,8 +525,70 @@
 bool TypesAreEqualUpToSign(opt::IRContext* ir_context, uint32_t type1_id,
                            uint32_t type2_id);
 
-}  // namespace fuzzerutil
+// Converts repeated field of UInt32Pair to a map. If two or more equal values
+// of |UInt32Pair::first()| are available in |data|, the last value of
+// |UInt32Pair::second()| is used.
+std::map<uint32_t, uint32_t> RepeatedUInt32PairToMap(
+    const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>& data);
 
+// Converts a map into a repeated field of UInt32Pair.
+google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>
+MapToRepeatedUInt32Pair(const std::map<uint32_t, uint32_t>& data);
+
+// Returns the last instruction in |block_id| before which an instruction with
+// opcode |opcode| can be inserted, or nullptr if there is no such instruction.
+opt::Instruction* GetLastInsertBeforeInstruction(opt::IRContext* ir_context,
+                                                 uint32_t block_id,
+                                                 SpvOp opcode);
+
+// Checks whether various conditions hold related to the acceptability of
+// replacing the id use at |use_in_operand_index| of |use_instruction| with a
+// synonym or another id of appropriate type if the original id is irrelevant.
+// In particular, this checks that:
+// - If id use is an index of an irrelevant id (|use_in_operand_index > 0|)
+//   in OpAccessChain - it can't be replaced.
+// - The id use is not an index into a struct field in an OpAccessChain - such
+//   indices must be constants, so it is dangerous to replace them.
+// - The id use is not a pointer function call argument, on which there are
+//   restrictions that make replacement problematic.
+// - The id use is not the Sample parameter of an OpImageTexelPointer
+//   instruction, as this must satisfy particular requirements.
+bool IdUseCanBeReplaced(opt::IRContext* ir_context,
+                        const TransformationContext& transformation_context,
+                        opt::Instruction* use_instruction,
+                        uint32_t use_in_operand_index);
+
+// Requires that |struct_type_id| is the id of a struct type, and (as per the
+// SPIR-V spec) that either all or none of the members of |struct_type_id| have
+// the BuiltIn decoration. Returns true if and only if all members have the
+// BuiltIn decoration.
+bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
+                                  uint32_t struct_type_id);
+
+// Returns true if and only if |id| is decorated with either Block or
+// BufferBlock.  Even though these decorations are only allowed on struct types,
+// for convenience |id| can be any result id so that it is possible to call this
+// method on something that *might* be a struct type.
+bool HasBlockOrBufferBlockDecoration(opt::IRContext* ir_context, uint32_t id);
+
+// Returns true iff splitting block |block_to_split| just before the instruction
+// |split_before| would separate an OpSampledImage instruction from its usage.
+bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+    opt::BasicBlock* block_to_split, opt::Instruction* split_before);
+
+// Returns true if the instruction given has no side effects.
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3758): Add any
+//  missing instructions to the list. In particular, GLSL extended instructions
+//  (called using OpExtInst) have not been considered.
+bool InstructionHasNoSideEffects(const opt::Instruction& instruction);
+
+// Returns a set of the ids of all the return blocks that are reachable from
+// the entry block of |function_id|.
+// Assumes that the function exists in the module.
+std::set<uint32_t> GetReachableReturnBlocks(opt::IRContext* ir_context,
+                                            uint32_t function_id);
+
+}  // namespace fuzzerutil
 }  // namespace fuzz
 }  // namespace spvtools
 
diff --git a/source/fuzz/id_use_descriptor.cpp b/source/fuzz/id_use_descriptor.cpp
index eb8589d..4e10146 100644
--- a/source/fuzz/id_use_descriptor.cpp
+++ b/source/fuzz/id_use_descriptor.cpp
@@ -52,7 +52,7 @@
     opt::IRContext* context, opt::Instruction* inst,
     uint32_t in_operand_index) {
   const auto& in_operand = inst->GetInOperand(in_operand_index);
-  assert(in_operand.type == SPV_OPERAND_TYPE_ID);
+  assert(spvIsInIdType(in_operand.type));
   return MakeIdUseDescriptor(in_operand.words[0],
                              MakeInstructionDescriptor(context, inst),
                              in_operand_index);
diff --git a/source/fuzz/instruction_message.cpp b/source/fuzz/instruction_message.cpp
index 44777ae..9503932 100644
--- a/source/fuzz/instruction_message.cpp
+++ b/source/fuzz/instruction_message.cpp
@@ -36,6 +36,18 @@
   return result;
 }
 
+protobufs::Instruction MakeInstructionMessage(
+    const opt::Instruction* instruction) {
+  opt::Instruction::OperandList input_operands;
+  for (uint32_t input_operand_index = 0;
+       input_operand_index < instruction->NumInOperands();
+       input_operand_index++) {
+    input_operands.push_back(instruction->GetInOperand(input_operand_index));
+  }
+  return MakeInstructionMessage(instruction->opcode(), instruction->type_id(),
+                                instruction->result_id(), input_operands);
+}
+
 std::unique_ptr<opt::Instruction> InstructionFromMessage(
     opt::IRContext* ir_context,
     const protobufs::Instruction& instruction_message) {
diff --git a/source/fuzz/instruction_message.h b/source/fuzz/instruction_message.h
index c010c2f..fcbb4c7 100644
--- a/source/fuzz/instruction_message.h
+++ b/source/fuzz/instruction_message.h
@@ -29,6 +29,10 @@
     SpvOp opcode, uint32_t result_type_id, uint32_t result_id,
     const opt::Instruction::OperandList& input_operands);
 
+// Creates an Instruction protobuf message from a parsed instruction.
+protobufs::Instruction MakeInstructionMessage(
+    const opt::Instruction* instruction);
+
 // Creates and returns an opt::Instruction from protobuf message
 // |instruction_message|, relative to |ir_context|.  In the process, the module
 // id bound associated with |ir_context| is updated to be at least as large as
diff --git a/source/fuzz/overflow_id_source.cpp b/source/fuzz/overflow_id_source.cpp
new file mode 100644
index 0000000..d900455
--- /dev/null
+++ b/source/fuzz/overflow_id_source.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+OverflowIdSource::~OverflowIdSource() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/overflow_id_source.h b/source/fuzz/overflow_id_source.h
new file mode 100644
index 0000000..cbf399a
--- /dev/null
+++ b/source/fuzz/overflow_id_source.h
@@ -0,0 +1,111 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_OVERFLOW_ID_SOURCE_H_
+#define SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_
+
+#include <cstdint>
+#include <unordered_set>
+
+namespace spvtools {
+namespace fuzz {
+
+// An implementation of this interface can be used to provide fresh ids on
+// demand when applying a transformation.
+//
+// During fuzzing this should never be required: a fuzzer pass should determine
+// all the fresh ids it requires to apply a transformation.
+//
+// However, during shrinking we can have the situation where, after removing
+// an early transformation, a later transformation needs more ids.
+//
+// As an example, suppose a SPIR-V function originally has this form:
+//
+// main() {
+//   stmt1;
+//   stmt2;
+//   stmt3;
+//   stmt4;
+// }
+//
+// Now suppose two *outlining* transformations are applied.  The first
+// transformation, T1, outlines "stmt1; stmt2;" into a function foo, giving us:
+//
+// foo() {
+//   stmt1;
+//   stmt2;
+// }
+//
+// main() {
+//   foo();
+//   stmt3;
+//   stmt4;
+// }
+//
+// The second transformation, T2, outlines "foo(); stmt3;" from main into a
+// function bar, giving us:
+//
+// foo() {
+//   stmt1;
+//   stmt2;
+// }
+//
+// bar() {
+//   foo();
+//   stmt3;
+// }
+//
+// main() {
+//   bar();
+//   stmt4;
+// }
+//
+// Suppose that T2 used a set of fresh ids, FRESH, in order to perform its
+// outlining.
+//
+// Now suppose that during shrinking we remove T1, but still want to apply T2.
+// The fresh ids used by T2 - FRESH - are sufficient to outline "foo(); stmt3;".
+// However, because we did not apply T1, "foo();" does not exist and instead the
+// task of T2 is to outline "stmt1; stmt2; stmt3;".  The set FRESH contains
+// *some* of the fresh ids required to do this (those for "stmt3;"), but not all
+// of them (those for "stmt1; stmt2;" are missing).
+//
+// A source of overflow ids can be used to allow the shrinker to proceed
+// nevertheless.
+//
+// It is desirable to use overflow ids only when needed.  In our worked example,
+// T2 should still use the ids from FRESH when handling "stmt3;", because later
+// transformations might refer to those ids and will become inapplicable if
+// overflow ids are used instead.
+class OverflowIdSource {
+ public:
+  virtual ~OverflowIdSource();
+
+  // Returns true if and only if this source is capable of providing overflow
+  // ids.
+  virtual bool HasOverflowIds() const = 0;
+
+  // Precondition: HasOverflowIds() must hold.  Returns the next available
+  // overflow id.
+  virtual uint32_t GetNextOverflowId() = 0;
+
+  // Returns the set of overflow ids from this source that have been previously
+  // issued via calls to GetNextOverflowId().
+  virtual const std::unordered_set<uint32_t>& GetIssuedOverflowIds() const = 0;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_
diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h
new file mode 100644
index 0000000..80ac087
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_instances.h
@@ -0,0 +1,187 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPEATED_PASS_INSTANCES_H_
+#define SOURCE_FUZZ_REPEATED_PASS_INSTANCES_H_
+
+#include "source/fuzz/fuzzer_pass_add_access_chains.h"
+#include "source/fuzz/fuzzer_pass_add_bit_instruction_synonyms.h"
+#include "source/fuzz/fuzzer_pass_add_composite_extract.h"
+#include "source/fuzz/fuzzer_pass_add_composite_inserts.h"
+#include "source/fuzz/fuzzer_pass_add_composite_types.h"
+#include "source/fuzz/fuzzer_pass_add_copy_memory.h"
+#include "source/fuzz/fuzzer_pass_add_dead_blocks.h"
+#include "source/fuzz/fuzzer_pass_add_dead_breaks.h"
+#include "source/fuzz/fuzzer_pass_add_dead_continues.h"
+#include "source/fuzz/fuzzer_pass_add_equation_instructions.h"
+#include "source/fuzz/fuzzer_pass_add_function_calls.h"
+#include "source/fuzz/fuzzer_pass_add_global_variables.h"
+#include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h"
+#include "source/fuzz/fuzzer_pass_add_loads.h"
+#include "source/fuzz/fuzzer_pass_add_local_variables.h"
+#include "source/fuzz/fuzzer_pass_add_loop_preheaders.h"
+#include "source/fuzz/fuzzer_pass_add_loops_to_create_int_constant_synonyms.h"
+#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
+#include "source/fuzz/fuzzer_pass_add_parameters.h"
+#include "source/fuzz/fuzzer_pass_add_relaxed_decorations.h"
+#include "source/fuzz/fuzzer_pass_add_stores.h"
+#include "source/fuzz/fuzzer_pass_add_synonyms.h"
+#include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h"
+#include "source/fuzz/fuzzer_pass_apply_id_synonyms.h"
+#include "source/fuzz/fuzzer_pass_construct_composites.h"
+#include "source/fuzz/fuzzer_pass_copy_objects.h"
+#include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h"
+#include "source/fuzz/fuzzer_pass_expand_vector_reductions.h"
+#include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h"
+#include "source/fuzz/fuzzer_pass_inline_functions.h"
+#include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
+#include "source/fuzz/fuzzer_pass_make_vector_operations_dynamic.h"
+#include "source/fuzz/fuzzer_pass_merge_blocks.h"
+#include "source/fuzz/fuzzer_pass_merge_function_returns.h"
+#include "source/fuzz/fuzzer_pass_mutate_pointers.h"
+#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
+#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_instructions.h"
+#include "source/fuzz/fuzzer_pass_propagate_instructions_down.h"
+#include "source/fuzz/fuzzer_pass_propagate_instructions_up.h"
+#include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
+#include "source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h"
+#include "source/fuzz/fuzzer_pass_replace_branches_from_dead_blocks_with_exits.h"
+#include "source/fuzz/fuzzer_pass_replace_copy_memories_with_loads_stores.h"
+#include "source/fuzz/fuzzer_pass_replace_copy_objects_with_stores_loads.h"
+#include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h"
+#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
+#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
+#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h"
+#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h"
+#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
+#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
+#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"
+
+namespace spvtools {
+namespace fuzz {
+
+// This class has a distinct member for each repeated fuzzer pass (i.e., a
+// fuzzer pass that it makes sense to run multiple times).  If a member is null
+// then we do not have an instance of that fuzzer pass, i.e. it is disabled.
+// The class also provides access to the set of passes that are enabled.
+class RepeatedPassInstances {
+// This macro should be invoked below for every repeated fuzzer pass.  If a
+// repeated fuzzer pass is called FuzzerPassFoo then the macro invocation:
+//
+//    REPEATED_PASS_INSTANCE(Foo);
+//
+// should be used.  This adds a private member of type FuzzerPassFoo*, and
+// provides the following public methods:
+//
+// // Requires that SetPass has not been called previously with FuzzerPassFoo.
+// // Adds |pass| to the set of known pass instances.
+// void SetPass(std::unique_ptr<FuzzerPassFoo> pass);
+//
+// // Returns a pointer to a pass instance of type FuzzerPassFoo that was
+// // previously registered via SetPass(), or nullptr if no such instance was
+// // registered
+// FuzzerPassFoo* GetFoo();
+#define REPEATED_PASS_INSTANCE(NAME)                                     \
+ public:                                                                 \
+  FuzzerPass##NAME* Get##NAME() const { return NAME##_; }                \
+  void SetPass(std::unique_ptr<FuzzerPass##NAME> pass) {                 \
+    assert(NAME##_ == nullptr && "Attempt to set pass multiple times."); \
+    NAME##_ = pass.get();                                                \
+    passes_.push_back(std::move(pass));                                  \
+  }                                                                      \
+                                                                         \
+ private:                                                                \
+  FuzzerPass##NAME* NAME##_ = nullptr
+
+  REPEATED_PASS_INSTANCE(AddAccessChains);
+  REPEATED_PASS_INSTANCE(AddBitInstructionSynonyms);
+  REPEATED_PASS_INSTANCE(AddCompositeExtract);
+  REPEATED_PASS_INSTANCE(AddCompositeInserts);
+  REPEATED_PASS_INSTANCE(AddCompositeTypes);
+  REPEATED_PASS_INSTANCE(AddCopyMemory);
+  REPEATED_PASS_INSTANCE(AddDeadBlocks);
+  REPEATED_PASS_INSTANCE(AddDeadBreaks);
+  REPEATED_PASS_INSTANCE(AddDeadContinues);
+  REPEATED_PASS_INSTANCE(AddEquationInstructions);
+  REPEATED_PASS_INSTANCE(AddFunctionCalls);
+  REPEATED_PASS_INSTANCE(AddGlobalVariables);
+  REPEATED_PASS_INSTANCE(AddImageSampleUnusedComponents);
+  REPEATED_PASS_INSTANCE(AddLoads);
+  REPEATED_PASS_INSTANCE(AddLocalVariables);
+  REPEATED_PASS_INSTANCE(AddLoopPreheaders);
+  REPEATED_PASS_INSTANCE(AddLoopsToCreateIntConstantSynonyms);
+  REPEATED_PASS_INSTANCE(AddOpPhiSynonyms);
+  REPEATED_PASS_INSTANCE(AddParameters);
+  REPEATED_PASS_INSTANCE(AddRelaxedDecorations);
+  REPEATED_PASS_INSTANCE(AddStores);
+  REPEATED_PASS_INSTANCE(AddSynonyms);
+  REPEATED_PASS_INSTANCE(AddVectorShuffleInstructions);
+  REPEATED_PASS_INSTANCE(ApplyIdSynonyms);
+  REPEATED_PASS_INSTANCE(ConstructComposites);
+  REPEATED_PASS_INSTANCE(CopyObjects);
+  REPEATED_PASS_INSTANCE(DonateModules);
+  REPEATED_PASS_INSTANCE(DuplicateRegionsWithSelections);
+  REPEATED_PASS_INSTANCE(ExpandVectorReductions);
+  REPEATED_PASS_INSTANCE(FlattenConditionalBranches);
+  REPEATED_PASS_INSTANCE(InlineFunctions);
+  REPEATED_PASS_INSTANCE(InvertComparisonOperators);
+  REPEATED_PASS_INSTANCE(MakeVectorOperationsDynamic);
+  REPEATED_PASS_INSTANCE(MergeBlocks);
+  REPEATED_PASS_INSTANCE(MergeFunctionReturns);
+  REPEATED_PASS_INSTANCE(MutatePointers);
+  REPEATED_PASS_INSTANCE(ObfuscateConstants);
+  REPEATED_PASS_INSTANCE(OutlineFunctions);
+  REPEATED_PASS_INSTANCE(PermuteBlocks);
+  REPEATED_PASS_INSTANCE(PermuteFunctionParameters);
+  REPEATED_PASS_INSTANCE(PermuteInstructions);
+  REPEATED_PASS_INSTANCE(PropagateInstructionsDown);
+  REPEATED_PASS_INSTANCE(PropagateInstructionsUp);
+  REPEATED_PASS_INSTANCE(PushIdsThroughVariables);
+  REPEATED_PASS_INSTANCE(ReplaceAddsSubsMulsWithCarryingExtended);
+  REPEATED_PASS_INSTANCE(ReplaceBranchesFromDeadBlocksWithExits);
+  REPEATED_PASS_INSTANCE(ReplaceCopyMemoriesWithLoadsStores);
+  REPEATED_PASS_INSTANCE(ReplaceCopyObjectsWithStoresLoads);
+  REPEATED_PASS_INSTANCE(ReplaceLoadsStoresWithCopyMemories);
+  REPEATED_PASS_INSTANCE(ReplaceIrrelevantIds);
+  REPEATED_PASS_INSTANCE(ReplaceOpPhiIdsFromDeadPredecessors);
+  REPEATED_PASS_INSTANCE(ReplaceOpSelectsWithConditionalBranches);
+  REPEATED_PASS_INSTANCE(ReplaceParameterWithGlobal);
+  REPEATED_PASS_INSTANCE(ReplaceLinearAlgebraInstructions);
+  REPEATED_PASS_INSTANCE(ReplaceParamsWithStruct);
+  REPEATED_PASS_INSTANCE(SplitBlocks);
+  REPEATED_PASS_INSTANCE(SwapBranchConditionalOperands);
+  REPEATED_PASS_INSTANCE(WrapRegionsInSelections);
+#undef REPEATED_PASS_INSTANCE
+
+ public:
+  // Yields the sequence of fuzzer pass instances that have been registered.
+  const std::vector<std::unique_ptr<FuzzerPass>>& GetPasses() const {
+    return passes_;
+  }
+
+ private:
+  // The distinct fuzzer pass instances that have been registered via SetPass().
+  std::vector<std::unique_ptr<FuzzerPass>> passes_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPEATED_PASS_INSTANCES_H_
diff --git a/source/fuzz/pass_management/repeated_pass_manager.cpp b/source/fuzz/pass_management/repeated_pass_manager.cpp
new file mode 100644
index 0000000..032f264
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager.cpp
@@ -0,0 +1,27 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pass_management/repeated_pass_manager.h"
+
+namespace spvtools {
+namespace fuzz {
+
+RepeatedPassManager::RepeatedPassManager(FuzzerContext* fuzzer_context,
+                                         RepeatedPassInstances* pass_instances)
+    : fuzzer_context_(fuzzer_context), pass_instances_(pass_instances) {}
+
+RepeatedPassManager::~RepeatedPassManager() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_manager.h b/source/fuzz/pass_management/repeated_pass_manager.h
new file mode 100644
index 0000000..1c23179
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPEATED_PASS_MANAGER_H_
+#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_H_
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass.h"
+#include "source/fuzz/pass_management/repeated_pass_instances.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// 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
+// been run and bias the selection of future passes according to this history.
+class RepeatedPassManager {
+ public:
+  RepeatedPassManager(FuzzerContext* fuzzer_context,
+                      RepeatedPassInstances* pass_instances);
+
+  virtual ~RepeatedPassManager();
+
+  // Returns the fuzzer pass instance that should be run next.  The
+  // transformations that have been applied so far are provided via
+  // |applied_transformations| and can be used to influence the decision.
+  virtual FuzzerPass* ChoosePass(
+      const protobufs::TransformationSequence& applied_transformations) = 0;
+
+ protected:
+  FuzzerContext* GetFuzzerContext() { return fuzzer_context_; }
+
+  RepeatedPassInstances* GetPassInstances() { return pass_instances_; }
+
+ private:
+  // Provided in order to allow the pass manager to make random decisions.
+  FuzzerContext* fuzzer_context_;
+
+  // The repeated fuzzer passes that are enabled.
+  RepeatedPassInstances* pass_instances_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPEATED_PASS_MANAGER_H_
diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp
new file mode 100644
index 0000000..e91ba35
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.cpp
@@ -0,0 +1,70 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pass_management/repeated_pass_manager_looped_with_recommendations.h"
+
+namespace spvtools {
+namespace fuzz {
+
+RepeatedPassManagerLoopedWithRecommendations::
+    RepeatedPassManagerLoopedWithRecommendations(
+        FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances,
+        RepeatedPassRecommender* pass_recommender)
+    : RepeatedPassManager(fuzzer_context, pass_instances),
+      num_transformations_applied_before_last_pass_choice_(0),
+      next_pass_index_(0) {
+  auto& passes = GetPassInstances()->GetPasses();
+  do {
+    FuzzerPass* current_pass =
+        passes[GetFuzzerContext()->RandomIndex(passes)].get();
+    pass_loop_.push_back(current_pass);
+    for (auto future_pass :
+         pass_recommender->GetFuturePassRecommendations(*current_pass)) {
+      recommended_pass_indices_.insert(
+          static_cast<uint32_t>(pass_loop_.size()));
+      pass_loop_.push_back(future_pass);
+    }
+  } while (fuzzer_context->ChoosePercentage(
+      fuzzer_context->GetChanceOfAddingAnotherPassToPassLoop()));
+}
+
+RepeatedPassManagerLoopedWithRecommendations::
+    ~RepeatedPassManagerLoopedWithRecommendations() = default;
+
+FuzzerPass* RepeatedPassManagerLoopedWithRecommendations::ChoosePass(
+    const protobufs::TransformationSequence& applied_transformations) {
+  assert((next_pass_index_ > 0 ||
+          recommended_pass_indices_.count(next_pass_index_) == 0) &&
+         "The first pass in the loop should not be a recommendation.");
+  assert(static_cast<uint32_t>(applied_transformations.transformation_size()) >=
+             num_transformations_applied_before_last_pass_choice_ &&
+         "The number of applied transformations should not decrease.");
+  if (num_transformations_applied_before_last_pass_choice_ ==
+      static_cast<uint32_t>(applied_transformations.transformation_size())) {
+    // The last pass that was applied did not lead to any new transformations.
+    // We thus do not want to apply recommendations based on it, so we skip on
+    // to the next non-recommended pass.
+    while (recommended_pass_indices_.count(next_pass_index_)) {
+      next_pass_index_ =
+          (next_pass_index_ + 1) % static_cast<uint32_t>(pass_loop_.size());
+    }
+  }
+  auto result = pass_loop_[next_pass_index_];
+  next_pass_index_ =
+      (next_pass_index_ + 1) % static_cast<uint32_t>(pass_loop_.size());
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h
new file mode 100644
index 0000000..42ce38e
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager_looped_with_recommendations.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_
+#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_
+
+#include <vector>
+
+#include "source/fuzz/pass_management/repeated_pass_manager.h"
+#include "source/fuzz/pass_management/repeated_pass_recommender.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// On construction, this pass manager creates a sequence of fuzzer passes which
+// is not changed thereafter.  Passes from this sequence are served up in round
+// robin fashion each time ChoosePass is invoked - i.e., the sequence is a "pass
+// loop".
+//
+// The pass loop is constructed by repeatedly:
+// - Randomly adding an enabled pass
+// - Adding all recommended follow-on passes for this pass
+// and probabilistically terminating this process.
+class RepeatedPassManagerLoopedWithRecommendations
+    : public RepeatedPassManager {
+ public:
+  RepeatedPassManagerLoopedWithRecommendations(
+      FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances,
+      RepeatedPassRecommender* pass_recommender);
+
+  ~RepeatedPassManagerLoopedWithRecommendations() override;
+
+  FuzzerPass* ChoosePass(const protobufs::TransformationSequence&
+                             applied_transformations) override;
+
+ private:
+  // The loop of fuzzer passes to be applied, populated on construction.
+  std::vector<FuzzerPass*> pass_loop_;
+
+  // A set of indices into |pass_loop_| recording which passes are in the loop
+  // because they are recommended based on previous passes in the loop.  This
+  // allows these recommended passes to be skipped if the passes they are
+  // meant to amplify had no effect.
+  std::unordered_set<uint32_t> recommended_pass_indices_;
+
+  // Used to detect when chosen passes have had no effect, so that their
+  // associated recommendations are skipped.
+  uint32_t num_transformations_applied_before_last_pass_choice_;
+
+  // An index into |pass_loop_| specifying which pass should be served up next
+  // time ChoosePass is invoked.
+  uint32_t next_pass_index_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPEATED_PASS_MANAGER_LOOPED_WITH_RECOMMENDATIONS_H_
diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp
new file mode 100644
index 0000000..920d13f
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.cpp
@@ -0,0 +1,60 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pass_management/repeated_pass_manager_random_with_recommendations.h"
+
+namespace spvtools {
+namespace fuzz {
+
+RepeatedPassManagerRandomWithRecommendations::
+    RepeatedPassManagerRandomWithRecommendations(
+        FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances,
+        RepeatedPassRecommender* pass_recommender)
+    : RepeatedPassManager(fuzzer_context, pass_instances),
+      pass_recommender_(pass_recommender),
+      num_transformations_applied_before_last_pass_choice_(0),
+      last_pass_choice_(nullptr) {}
+
+RepeatedPassManagerRandomWithRecommendations::
+    ~RepeatedPassManagerRandomWithRecommendations() = default;
+
+FuzzerPass* RepeatedPassManagerRandomWithRecommendations::ChoosePass(
+    const protobufs::TransformationSequence& applied_transformations) {
+  assert(static_cast<uint32_t>(applied_transformations.transformation_size()) >=
+             num_transformations_applied_before_last_pass_choice_ &&
+         "The number of applied transformations should not decrease.");
+  if (last_pass_choice_ != nullptr &&
+      static_cast<uint32_t>(applied_transformations.transformation_size()) >
+          num_transformations_applied_before_last_pass_choice_) {
+    // The last pass had some effect, so we make future recommendations based on
+    // it.
+    for (auto future_pass :
+         pass_recommender_->GetFuturePassRecommendations(*last_pass_choice_)) {
+      recommended_passes_.push_back(future_pass);
+    }
+  }
+
+  FuzzerPass* result;
+  if (recommended_passes_.empty() || GetFuzzerContext()->ChooseEven()) {
+    auto& passes = GetPassInstances()->GetPasses();
+    result = passes[GetFuzzerContext()->RandomIndex(passes)].get();
+  } else {
+    result = recommended_passes_.front();
+    recommended_passes_.pop_front();
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h
new file mode 100644
index 0000000..5dbd455
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager_random_with_recommendations.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_
+#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_
+
+#include <deque>
+
+#include "source/fuzz/pass_management/repeated_pass_manager.h"
+#include "source/fuzz/pass_management/repeated_pass_recommender.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This repeated pass manager uses a pass recommender to recommend future passes
+// each time a fuzzer pass is run.  It keeps a queue of recommended passes.
+//
+// Each time a fuzzer pass is requested, the manager either selects an enabled
+// fuzzer pass at random, or selects the pass at the front of the recommendation
+// queue, removing it from the queue.  The decision of which of these pass
+// selection methods to use is made randomly each time ChoosePass is called.
+//
+// Either way, recommended follow-on passes for the chosen pass are added to
+// the recommendation queue.
+class RepeatedPassManagerRandomWithRecommendations
+    : public RepeatedPassManager {
+ public:
+  RepeatedPassManagerRandomWithRecommendations(
+      FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances,
+      RepeatedPassRecommender* pass_recommender);
+
+  ~RepeatedPassManagerRandomWithRecommendations() override;
+
+  FuzzerPass* ChoosePass(const protobufs::TransformationSequence&
+                             applied_transformations) override;
+
+ private:
+  // The queue of passes that have been recommended based on previously-chosen
+  // passes.
+  std::deque<FuzzerPass*> recommended_passes_;
+
+  // Used to recommend future passes.
+  RepeatedPassRecommender* pass_recommender_;
+
+  // Used to detect when chosen passes have had no effect, so that their
+  // associated recommendations are skipped.
+  uint32_t num_transformations_applied_before_last_pass_choice_;
+
+  // The fuzzer pass returned last time ChoosePass() was called; nullptr if
+  // ChoosePass() has not yet been called.
+  FuzzerPass* last_pass_choice_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPEATED_PASS_MANAGER_RANDOM_WITH_RECOMMENDATIONS_H_
diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.cpp b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp
new file mode 100644
index 0000000..c593ffe
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager_simple.cpp
@@ -0,0 +1,33 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pass_management/repeated_pass_manager_simple.h"
+
+namespace spvtools {
+namespace fuzz {
+
+RepeatedPassManagerSimple::RepeatedPassManagerSimple(
+    FuzzerContext* fuzzer_context, RepeatedPassInstances* pass_instances)
+    : RepeatedPassManager(fuzzer_context, pass_instances) {}
+
+RepeatedPassManagerSimple::~RepeatedPassManagerSimple() = default;
+
+FuzzerPass* RepeatedPassManagerSimple::ChoosePass(
+    const protobufs::TransformationSequence& /*unused*/) {
+  auto& passes = GetPassInstances()->GetPasses();
+  return passes[GetFuzzerContext()->RandomIndex(passes)].get();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_manager_simple.h b/source/fuzz/pass_management/repeated_pass_manager_simple.h
new file mode 100644
index 0000000..a4cc652
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_manager_simple.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPEATED_PASS_MANAGER_SIMPLE_H_
+#define SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_
+
+#include "source/fuzz/pass_management/repeated_pass_manager.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Selects the next pass to run uniformly at random from the enabled repeated
+// passes.  Recommendations are not used.
+class RepeatedPassManagerSimple : public RepeatedPassManager {
+ public:
+  RepeatedPassManagerSimple(FuzzerContext* fuzzer_context,
+                            RepeatedPassInstances* pass_instances);
+
+  ~RepeatedPassManagerSimple() override;
+
+  FuzzerPass* ChoosePass(const protobufs::TransformationSequence&
+                             applied_transformations) override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPEATED_PASS_MANAGER_SIMPLE_H_
diff --git a/source/fuzz/pass_management/repeated_pass_recommender.cpp b/source/fuzz/pass_management/repeated_pass_recommender.cpp
new file mode 100644
index 0000000..c7789dc
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_recommender.cpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pass_management/repeated_pass_recommender.h"
+
+namespace spvtools {
+namespace fuzz {
+
+RepeatedPassRecommender::~RepeatedPassRecommender() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_recommender.h b/source/fuzz/pass_management/repeated_pass_recommender.h
new file mode 100644
index 0000000..a6b1338
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_recommender.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPEATED_PASS_RECOMMENDER_H_
+#define SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_
+
+#include <vector>
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Interface for influencing interactions between repeated fuzzer passes, by
+// allowing hints as to which passes are recommended to be run after one
+// another.
+class RepeatedPassRecommender {
+ public:
+  virtual ~RepeatedPassRecommender();
+
+  // Given a reference to a repeated pass, |pass|, returns a sequence of
+  // repeated pass instances that might be worth running soon after having
+  // run |pass|.
+  virtual std::vector<FuzzerPass*> GetFuturePassRecommendations(
+      const FuzzerPass& pass) = 0;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_H_
diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
new file mode 100644
index 0000000..a933848
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
@@ -0,0 +1,377 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/pass_management/repeated_pass_recommender_standard.h"
+
+#include <numeric>
+
+namespace spvtools {
+namespace fuzz {
+
+RepeatedPassRecommenderStandard::RepeatedPassRecommenderStandard(
+    RepeatedPassInstances* pass_instances, FuzzerContext* fuzzer_context)
+    : pass_instances_(pass_instances), fuzzer_context_(fuzzer_context) {}
+
+RepeatedPassRecommenderStandard::~RepeatedPassRecommenderStandard() = default;
+
+std::vector<FuzzerPass*>
+RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
+    const FuzzerPass& pass) {
+  if (&pass == pass_instances_->GetAddAccessChains()) {
+    // - Adding access chains means there is more scope for loading and storing
+    // - It could be worth making more access chains from the recently-added
+    //   access chains
+    return RandomOrderAndNonNull({pass_instances_->GetAddLoads(),
+                                  pass_instances_->GetAddStores(),
+                                  pass_instances_->GetAddAccessChains()});
+  }
+  if (&pass == pass_instances_->GetAddBitInstructionSynonyms()) {
+    // - Adding bit instruction synonyms creates opportunities to apply synonyms
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
+  if (&pass == pass_instances_->GetAddCompositeExtract()) {
+    // - This transformation can introduce synonyms to the fact manager.
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
+  if (&pass == pass_instances_->GetAddCompositeInserts()) {
+    // - Having added inserts we will have more vectors, so there is scope for
+    //   vector shuffling
+    // - Adding inserts creates synonyms, which we should try to use
+    // - Vector inserts can be made dynamic
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetAddVectorShuffleInstructions(),
+         pass_instances_->GetApplyIdSynonyms(),
+         pass_instances_->GetMakeVectorOperationsDynamic()});
+  }
+  if (&pass == pass_instances_->GetAddCompositeTypes()) {
+    // - More composite types gives more scope for constructing composites
+    return RandomOrderAndNonNull({pass_instances_->GetConstructComposites()});
+  }
+  if (&pass == pass_instances_->GetAddCopyMemory()) {
+    // - Recently-added copy memories could be replace with load-store pairs
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()});
+  }
+  if (&pass == pass_instances_->GetAddDeadBlocks()) {
+    // - Dead blocks are great for adding function calls
+    // - Dead blocks are also great for adding loads and stores
+    // - The guard associated with a dead block can be obfuscated
+    // - Branches from dead blocks may be replaced with exits
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetAddFunctionCalls(), pass_instances_->GetAddLoads(),
+         pass_instances_->GetAddStores(),
+         pass_instances_->GetObfuscateConstants(),
+         pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()});
+  }
+  if (&pass == pass_instances_->GetAddDeadBreaks()) {
+    // - The guard of the dead break is a good candidate for obfuscation
+    return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()});
+  }
+  if (&pass == pass_instances_->GetAddDeadContinues()) {
+    // - The guard of the dead continue is a good candidate for obfuscation
+    return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()});
+  }
+  if (&pass == pass_instances_->GetAddEquationInstructions()) {
+    // - Equation instructions can create synonyms, which we can apply
+    // - Equation instructions collaborate with one another to make synonyms, so
+    //   having added some it is worth adding more
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetApplyIdSynonyms(),
+         pass_instances_->GetAddEquationInstructions()});
+  }
+  if (&pass == pass_instances_->GetAddFunctionCalls()) {
+    // - Called functions can be inlined
+    // - Irrelevant ids are created, so they can be replaced
+    return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions(),
+                                  pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetAddGlobalVariables()) {
+    // - New globals provide new possibilities for making access chains
+    // - We can load from and store to new globals
+    return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(),
+                                  pass_instances_->GetAddLoads(),
+                                  pass_instances_->GetAddStores()});
+  }
+  if (&pass == pass_instances_->GetAddImageSampleUnusedComponents()) {
+    // - This introduces an unused component whose id is irrelevant and can be
+    //   replaced
+    return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetAddLoads()) {
+    // - Loads might end up with corresponding stores, so that pairs can be
+    //   replaced with memory copies
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
+  }
+  if (&pass == pass_instances_->GetAddLocalVariables()) {
+    // - New locals provide new possibilities for making access chains
+    // - We can load from and store to new locals
+    return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(),
+                                  pass_instances_->GetAddLoads(),
+                                  pass_instances_->GetAddStores()});
+  }
+  if (&pass == pass_instances_->GetAddLoopPreheaders()) {
+    // - The loop preheader provides more scope for duplicating regions and
+    //   outlining functions.
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetDuplicateRegionsWithSelections(),
+         pass_instances_->GetOutlineFunctions(),
+         pass_instances_->GetWrapRegionsInSelections()});
+  }
+  if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) {
+    // - New synonyms can be applied
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
+  if (&pass == pass_instances_->GetAddOpPhiSynonyms()) {
+    // - New synonyms can be applied
+    // - If OpPhi synonyms are introduced for blocks with dead predecessors, the
+    //   values consumed from dead predecessors can be replaced
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetApplyIdSynonyms(),
+         pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()});
+  }
+  if (&pass == pass_instances_->GetAddParameters()) {
+    // - We might be able to create interesting synonyms of new parameters.
+    // - This introduces irrelevant ids, which can be replaced
+    return RandomOrderAndNonNull({pass_instances_->GetAddSynonyms(),
+                                  pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetAddRelaxedDecorations()) {
+    // - No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetAddStores()) {
+    // - Stores might end up with corresponding loads, so that pairs can be
+    //   replaced with memory copies
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
+  }
+  if (&pass == pass_instances_->GetAddSynonyms()) {
+    // - New synonyms can be applied
+    // - Synonym instructions use constants, which can be obfuscated
+    // - Synonym instructions use irrelevant ids, which can be replaced
+    // - Synonym instructions introduce addition/subtraction, which can be
+    //   replaced with carrying/extended versions
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetApplyIdSynonyms(),
+         pass_instances_->GetObfuscateConstants(),
+         pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended(),
+         pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetAddVectorShuffleInstructions()) {
+    // - Vector shuffles create synonyms that can be applied
+    // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806) Extract
+    //    from composites.
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
+  if (&pass == pass_instances_->GetApplyIdSynonyms()) {
+    // - No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetConstructComposites()) {
+    // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806): Extract
+    //    from composites.
+    return RandomOrderAndNonNull({});
+  }
+  if (&pass == pass_instances_->GetCopyObjects()) {
+    // - Object copies create synonyms that can be applied
+    // - OpCopyObject can be replaced with a store/load pair
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetApplyIdSynonyms(),
+         pass_instances_->GetReplaceCopyObjectsWithStoresLoads()});
+  }
+  if (&pass == pass_instances_->GetDonateModules()) {
+    // - New functions in the module can be called
+    // - Donated dead functions produce irrelevant ids, which can be replaced
+    // - Donated functions are good candidates for having their returns merged
+    // - Donated dead functions may allow branches to be replaced with exits
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetAddFunctionCalls(),
+         pass_instances_->GetReplaceIrrelevantIds(),
+         pass_instances_->GetMergeFunctionReturns(),
+         pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()});
+  }
+  if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) {
+    // - Parts of duplicated regions can be outlined
+    return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
+  }
+  if (&pass == pass_instances_->GetExpandVectorReductions()) {
+    // - Adding OpAny and OpAll synonyms creates opportunities to apply synonyms
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
+  if (&pass == pass_instances_->GetFlattenConditionalBranches()) {
+    // - Parts of flattened selections can be outlined
+    // - The flattening transformation introduces constants and irrelevant ids
+    //   for enclosing hard-to-flatten operations; these can be obfuscated or
+    //   replaced
+    return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants(),
+                                  pass_instances_->GetOutlineFunctions(),
+                                  pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetInlineFunctions()) {
+    // - Parts of inlined functions can be outlined again
+    return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
+  }
+  if (&pass == pass_instances_->GetInvertComparisonOperators()) {
+    // - No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetMakeVectorOperationsDynamic()) {
+    // - No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetMergeBlocks()) {
+    // - Having merged some blocks it may be interesting to split them in a
+    //   different way
+    return RandomOrderAndNonNull({pass_instances_->GetSplitBlocks()});
+  }
+  if (&pass == pass_instances_->GetMergeFunctionReturns()) {
+    // - Functions without early returns are more likely to be able to be
+    //   inlined.
+    return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions()});
+  }
+  if (&pass == pass_instances_->GetMutatePointers()) {
+    // - This creates irrelevant ids, which can be replaced
+    return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetObfuscateConstants()) {
+    // - No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetOutlineFunctions()) {
+    // - This creates more functions, which can be called
+    // - Inlining the function for the region that was outlined might also be
+    //   fruitful; it will be inlined in a different form
+    return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
+                                  pass_instances_->GetInlineFunctions()});
+  }
+  if (&pass == pass_instances_->GetPermuteBlocks()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetPermuteFunctionParameters()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetPermuteInstructions()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetPropagateInstructionsDown()) {
+    // - This fuzzer pass might create new synonyms that can later be applied.
+    // - This fuzzer pass might create irrelevant ids that can later be
+    //   replaced.
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms(),
+                                  pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetPropagateInstructionsUp()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetPushIdsThroughVariables()) {
+    // - This pass creates synonyms, so it is worth applying them
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
+  if (&pass == pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()) {
+    // - Changing a branch to OpReturnValue introduces an irrelevant id, which
+    //   can be replaced
+    return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
+  }
+  if (&pass == pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceCopyObjectsWithStoresLoads()) {
+    // - We may end up with load/store pairs that could be used to create memory
+    //   copies
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
+  }
+  if (&pass == pass_instances_->GetReplaceIrrelevantIds()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceLinearAlgebraInstructions()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceLoadsStoresWithCopyMemories()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceOpSelectsWithConditionalBranches()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceParameterWithGlobal()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetReplaceParamsWithStruct()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetSplitBlocks()) {
+    // - More blocks means more chances for adding dead breaks/continues, and
+    //   for adding dead blocks
+    return RandomOrderAndNonNull({pass_instances_->GetAddDeadBreaks(),
+                                  pass_instances_->GetAddDeadContinues(),
+                                  pass_instances_->GetAddDeadBlocks()});
+  }
+  if (&pass == pass_instances_->GetSwapBranchConditionalOperands()) {
+    // No obvious follow-on passes
+    return {};
+  }
+  if (&pass == pass_instances_->GetWrapRegionsInSelections()) {
+    // - This pass uses an irrelevant boolean constant - we can replace it with
+    //   something more interesting.
+    // - We can obfuscate that very constant as well.
+    // - We can flatten created selection construct.
+    return RandomOrderAndNonNull(
+        {pass_instances_->GetObfuscateConstants(),
+         pass_instances_->GetReplaceIrrelevantIds(),
+         pass_instances_->GetFlattenConditionalBranches()});
+  }
+  assert(false && "Unreachable: every fuzzer pass should be dealt with.");
+  return {};
+}
+
+std::vector<FuzzerPass*> RepeatedPassRecommenderStandard::RandomOrderAndNonNull(
+    const std::vector<FuzzerPass*>& passes) {
+  std::vector<uint32_t> indices(passes.size());
+  std::iota(indices.begin(), indices.end(), 0);
+  std::vector<FuzzerPass*> result;
+  while (!indices.empty()) {
+    FuzzerPass* maybe_pass =
+        passes[fuzzer_context_->RemoveAtRandomIndex(&indices)];
+    if (maybe_pass != nullptr &&
+        fuzzer_context_->ChoosePercentage(
+            fuzzer_context_
+                ->GetChanceOfAcceptingRepeatedPassRecommendation())) {
+      result.push_back(maybe_pass);
+    }
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.h b/source/fuzz/pass_management/repeated_pass_recommender_standard.h
new file mode 100644
index 0000000..293b8e0
--- /dev/null
+++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.h
@@ -0,0 +1,50 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REPEATED_PASS_RECOMMENDER_STANDARD_H_
+#define SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_STANDARD_H_
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/pass_management/repeated_pass_instances.h"
+#include "source/fuzz/pass_management/repeated_pass_recommender.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A manually-crafter recommender of repeated passes, designed based on
+// knowledge of how the various fuzzer passes work and speculation as to how
+// they might interact in interesting ways.
+class RepeatedPassRecommenderStandard : public RepeatedPassRecommender {
+ public:
+  RepeatedPassRecommenderStandard(RepeatedPassInstances* pass_instances,
+                                  FuzzerContext* fuzzer_context);
+
+  ~RepeatedPassRecommenderStandard();
+
+  std::vector<FuzzerPass*> GetFuturePassRecommendations(
+      const FuzzerPass& pass) override;
+
+ private:
+  std::vector<FuzzerPass*> RandomOrderAndNonNull(
+      const std::vector<FuzzerPass*>& passes);
+
+  RepeatedPassInstances* pass_instances_;
+
+  FuzzerContext* fuzzer_context_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_REPEATED_PASS_RECOMMENDER_STANDARD_H_
diff --git a/source/fuzz/protobufs/spirvfuzz_protobufs.h b/source/fuzz/protobufs/spirvfuzz_protobufs.h
index 26b8672..eb8cb14 100644
--- a/source/fuzz/protobufs/spirvfuzz_protobufs.h
+++ b/source/fuzz/protobufs/spirvfuzz_protobufs.h
@@ -24,6 +24,7 @@
 #if defined(__clang__)
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wunused-parameter"
+#pragma clang diagnostic ignored "-Wshadow"
 #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 3aa5675..770a2dd 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -306,6 +306,133 @@
 
 }
 
+message SideEffectWrapperInfo {
+  // When flattening a conditional branch, it is necessary to enclose
+  // instructions that have side effects inside conditionals, so that
+  // they are only executed if the condition holds. Otherwise, there
+  // might be unintended changes in memory, or crashes that would not
+  // originally happen.
+  // For example, the instruction %id = OpLoad %type %ptr, found in
+  // the true branch of the conditional, will be enclosed in a new
+  // conditional (assuming that the block containing it can be split
+  // around it) as follows:
+  //
+  //   [previous instructions in the block]
+  //                        OpSelectionMerge %merge_block_id None
+  //                        OpBranchConditional %cond %execute_block_id %alternative_block_id
+  //    %execute_block_id = OpLabel
+  //    %actual_result_id = OpLoad %type %ptr
+  //                       OpBranch %merge_block_id
+  //  %alternative_block_id = OpLabel
+  // %placeholder_result_id = OpCopyObject %type %value_to_copy_id
+  //                       OpBranch %merge_block_id
+  //     %merge_block_id = OpLabel
+  //                 %id = OpPhi %type %actual_result_id %execute_block_id %placeholder_result_id %alternative_block_id
+  //   [following instructions from the original block]
+  //
+  // If the instruction does not have a result id, this is simplified.
+  // For example, OpStore %ptr %value, found in the true branch of a
+  // conditional, is enclosed as follows:
+  //
+  //   [previous instructions in the block]
+  //                       OpSelectionMerge %merge_block None
+  //                       OpBranchConditional %cond %execute_block_id %merge_block_id
+  //   %execute_block_id = OpLabel
+  //                       OpStore %ptr %value
+  //                       OpBranch %merge_block_id
+  //     %merge_block_id = OpLabel
+  //   [following instructions from the original block]
+  //
+  // The same happens if the instruction is found in the false branch
+  // of the conditional being flattened, except that the label ids in
+  // the OpBranchConditional are swapped.
+
+
+  // An instruction descriptor for identifying the instruction to be
+  // enclosed inside a conditional. An instruction descriptor is
+  // necessary because the instruction might not have a result id.
+  InstructionDescriptor instruction = 1;
+
+  // A fresh id for the new merge block.
+  uint32 merge_block_id = 2;
+
+  // A fresh id for the new block where the actual instruction is
+  // executed.
+  uint32 execute_block_id = 3;
+
+  // The following fields are only needed if the original instruction has a
+  // result id. They can be set to 0 if not needed.
+
+  // A fresh id for the result id of the instruction (the original
+  // one is used by the OpPhi instruction).
+  uint32 actual_result_id = 4;
+
+  // A fresh id for the new block where the placeholder instruction
+  // is placed.
+  uint32 alternative_block_id = 5;
+
+  // A fresh id for the placeholder instruction.
+  uint32 placeholder_result_id = 6;
+
+  // An id present in the module, available to use at this point in
+  // the program and with the same type as the original instruction,
+  // that can be used to create a placeholder OpCopyObject
+  // instruction.
+  uint32 value_to_copy_id = 7;
+}
+
+message ReturnMergingInfo {
+  // TransformationMergeFunctionReturns needs to modify each merge block of
+  // loops containing return instructions, by:
+  // - adding instructions to decide whether the function is returning
+  // - adding instructions to pass on the return value of the function,
+  //   if it is returning
+  // - changing the branch instruction (which must be an unconditional branch)
+  //   to a conditional branch that, if the function is returning, branches to
+  //   the merge block of the innermost loop that contains this merge block
+  //   (which can be the new merge block introduced by the transformation).
+  //
+  // One such merge block of the form:
+  //  %block = OpLabel
+  //   %phi1 = OpPhi %type1 %val1_1 %pred1 %val1_2 %pred2
+  //   %phi2 = OpPhi %type2 %val2_1 %pred1 %val2_2 %pred2
+  //           OpBranch %next
+  //
+  // is transformed into:
+  //               %block = OpLabel
+  //     %is_returning_id = OpPhi %bool %false %pred1 %false %pred2 %true %ret_bb1 %is_bb2_returning %mer_bb2
+  // %maybe_return_val_id = OpPhi %return_type %any_returnable_val %pred1 %any_returnable_val %pred2
+  //                                            %ret_val1 %ret_bb1 %ret_val2 %mer_bb2
+  //                %phi1 = OpPhi %type1 %val1_1 %pred1 %val1_2 %pred2
+  //                                     %any_suitable_id_1 %ret_bb1 %any_suitable_id_1 %mer_bb2
+  //                %phi2 = OpPhi %type2 %val2_1 %pred1 %val2_2 %pred2
+  //                                     %any_suitable_id_1 %ret_bb1 %any_suitable_id_1 %mer_bb2
+  //                        OpBranchConditional %is_returning_id %innermost_loop_merge %next
+  //
+  // where %ret_bb1 is a block that originally contains a return instruction and %mer_bb2 is the merge block of an inner
+  // loop, from where the function might be returning.
+  //
+  // Note that the block is required to only have OpLabel, OpPhi or OpBranch instructions.
+
+  // The id of the merge block that needs to be modified.
+  uint32 merge_block_id = 1;
+
+  // A fresh id for a boolean OpPhi whose value will be true iff the function
+  // is returning. This will be used to decide whether to break out of the loop
+  // or to use the original branch of the function. This value will also be
+  // used by the merge block of the enclosing loop (if there is one) if the
+  // function is returning from this block.
+  uint32 is_returning_id = 2;
+
+  // A fresh id that will get the value being returned, if the function is
+  // returning. If the function return type is void, this is ignored.
+  uint32 maybe_return_val_id = 3;
+
+  // A mapping from each existing OpPhi id to a suitable id of the same type
+  // available to use before the instruction.
+  repeated UInt32Pair opphi_to_suitable_id = 4;
+}
+
 message LoopLimiterInfo {
 
   // Structure capturing the information required to manipulate a loop limiter
@@ -353,7 +480,7 @@
     TransformationAddTypeInt add_type_int = 7;
     TransformationAddDeadBreak add_dead_break = 8;
     TransformationReplaceBooleanConstantWithConstantBinary
-      replace_boolean_constant_with_constant_binary = 9;
+        replace_boolean_constant_with_constant_binary = 9;
     TransformationAddTypePointer add_type_pointer = 10;
     TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11;
     TransformationAddDeadContinue add_dead_continue = 12;
@@ -407,6 +534,29 @@
     TransformationReplaceCopyObjectWithStoreLoad replace_copy_object_with_store_load = 60;
     TransformationReplaceCopyMemoryWithLoadStore replace_copy_memory_with_load_store = 61;
     TransformationReplaceLoadStoreWithCopyMemory replace_load_store_with_copy_memory = 62;
+    TransformationAddLoopPreheader add_loop_preheader = 63;
+    TransformationMoveInstructionDown move_instruction_down = 64;
+    TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65;
+    TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66;
+    TransformationPropagateInstructionUp propagate_instruction_up = 67;
+    TransformationCompositeInsert composite_insert = 68;
+    TransformationInlineFunction inline_function = 69;
+    TransformationAddOpPhiSynonym add_opphi_synonym = 70;
+    TransformationMutatePointer mutate_pointer = 71;
+    TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
+    TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73;
+    TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74;
+    TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75;
+    TransformationFlattenConditionalBranch flatten_conditional_branch = 76;
+    TransformationAddBitInstructionSynonym add_bit_instruction_synonym = 77;
+    TransformationAddLoopToCreateIntConstantSynonym add_loop_to_create_int_constant_synonym = 78;
+    TransformationWrapRegionInSelection wrap_region_in_selection = 79;
+    TransformationAddEarlyTerminatorWrapper add_early_terminator_wrapper = 80;
+    TransformationPropagateInstructionDown propagate_instruction_down = 81;
+    TransformationReplaceBranchFromDeadBlockWithExit replace_branch_from_dead_block_with_exit = 82;
+    TransformationWrapEarlyTerminatorInFunction wrap_early_terminator_in_function = 83;
+    TransformationMergeFunctionReturns merge_function_returns = 84;
+    TransformationExpandVectorReduction expand_vector_reduction = 85;
     // Add additional option using the next available number.
   }
 }
@@ -443,6 +593,33 @@
 
 }
 
+message TransformationAddBitInstructionSynonym {
+
+  // A transformation that adds synonyms for bit instructions by evaluating
+  // each bit with the corresponding operation. There is a SPIR-V code example in the
+  // header file of the transformation class that can help understand the transformation.
+
+  // This transformation is only applicable if the described instruction has one of the following opcodes.
+  // Supported:
+  //   OpBitwiseOr
+  //   OpBitwiseXor
+  //   OpBitwiseAnd
+  //   OpNot
+  // To be supported in the future:
+  //   OpShiftRightLogical
+  //   OpShiftRightArithmetic
+  //   OpShiftLeftLogical
+  //   OpBitReverse
+  //   OpBitCount
+
+  // The bit instruction result id.
+  uint32 instruction_result_id = 1;
+
+  // The fresh ids required to apply the transformation.
+  repeated uint32 fresh_ids = 2;
+
+}
+
 message TransformationAddConstantBoolean {
 
   // Supports adding the constants true and false to a module, which may be
@@ -593,6 +770,28 @@
 
 }
 
+message TransformationAddEarlyTerminatorWrapper {
+
+  // Adds a function to the module containing a single block with a single non-
+  // label instruction that is either OpKill, OpUnreachable, or
+  // OpTerminateInvocation.  The purpose of this is to allow such instructions
+  // to be subsequently replaced with wrapper functions, which can then enable
+  // transformations (such as inlining) that are hard in the direct presence
+  // of these instructions.
+
+  // Fresh id for the function.
+  uint32 function_fresh_id = 1;
+
+  // Fresh id for the single basic block in the function.
+  uint32 label_fresh_id = 2;
+
+  // One of OpKill, OpUnreachable, OpTerminateInvocation.  If additional early
+  // termination instructions are added to SPIR-V they should also be handled
+  // here.
+  uint32 opcode = 3;
+
+}
+
 message TransformationAddFunction {
 
   // Adds a SPIR-V function to the module.
@@ -620,11 +819,12 @@
   // Id of an existing global value with the same return type as the function
   // that can be used to replace OpKill and OpReachable instructions with
   // ReturnValue instructions.  Ignored if the function has void return type.
+  // Only relevant if |is_livesafe| holds.
   uint32 kill_unreachable_return_value_id = 6;
 
   // A mapping (represented as a sequence) from every access chain result id in
   // the function to the ids required to clamp its indices to ensure they are in
-  // bounds.
+  // bounds; only relevant if |is_livesafe| holds.
   repeated AccessChainClampingInfo access_chain_clamping_info = 7;
 
 }
@@ -702,6 +902,125 @@
 
 }
 
+message TransformationAddLoopPreheader {
+
+  // A transformation that adds a loop preheader block before the given loop header.
+
+  // The id of the loop header block
+  uint32 loop_header_block = 1;
+
+  // A fresh id for the preheader block
+  uint32 fresh_id = 2;
+
+  // Fresh ids for splitting the OpPhi instructions in the header.
+  // A new OpPhi instruction in the preheader is needed for each OpPhi instruction in the header,
+  // if the header has more than one predecessor outside of the loop.
+  // This allows turning instructions of the form:
+  //
+  //   %loop_header_block = OpLabel
+  //                 %id1 = OpPhi %type %val1 %pred1_id %val2 %pred2_id %val3 %backedge_block_id
+  //
+  // into:
+  //            %fresh_id = OpLabel
+  //             %phi_id1 = OpPhi %type %val1 %pred1_id %val2 %pred2_id
+  //                        OpBranch %header_id
+  //   %loop_header_block = OpLabel
+  //                 %id1 = OpPhi %type %phi_id1 %fresh_id %val3 %backedge_block_id
+  repeated uint32 phi_id = 3;
+
+}
+
+message TransformationAddLoopToCreateIntConstantSynonym {
+  // A transformation that uses a loop to create a synonym for an integer
+  // constant C (scalar or vector) using an initial value I, a step value S and
+  // a number of iterations N such that C = I - N * S. For each iteration, S is
+  // subtracted from the total.
+  // The loop can be made up of one or two blocks, and it is inserted before a
+  // block with a single predecessor. In the one-block case, it is of the form:
+  //
+  //            %loop_id = OpLabel
+  //             %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id
+  //            %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id
+  //    %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id
+  // %incremented_ctr_id = OpIAdd %int %ctr_id %int_1
+  //            %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id
+  //                       OpLoopMerge %block_after_loop_id %loop_id None
+  //                       OpBranchConditional %cond_id %loop_id %block_after_loop_id
+  //
+  // A new OpPhi instruction is then added to %block_after_loop_id, as follows:
+  //
+  //  %block_after_loop_id = OpLabel
+  //               %syn_id = OpPhi %type_of_I %eventual_syn_id %loop_id
+  //
+  // This can be translated, assuming that N > 0, to:
+  // int syn = I;
+  // for (int ctr = 0; ctr < N; ctr++) syn = syn - S;
+  //
+  // All existing OpPhi instructions in %block_after_loop_id are also updated
+  // to reflect the fact that its predecessor is now %loop_id.
+
+  // The following are existing ids.
+
+  // The id of the integer constant C that we want a synonym of.
+  uint32 constant_id = 1;
+
+  // The id of the initial value integer constant I.
+  uint32 initial_val_id = 2;
+
+  // The id of the step value integer constant S.
+  uint32 step_val_id = 3;
+
+  // The id of the integer scalar constant, its value being the number of
+  // iterations N.
+  uint32 num_iterations_id = 4;
+
+  // The label id of the block before which the loop must be inserted.
+  uint32 block_after_loop_id = 5;
+
+
+  // The following are fresh ids.
+
+  // A fresh id for the synonym.
+  uint32 syn_id = 6;
+
+  // A fresh id for the label of the loop,
+  uint32 loop_id = 7;
+
+  // A fresh id for the counter.
+  uint32 ctr_id = 8;
+
+  // A fresh id taking the value I - S * ctr at the ctr-th iteration.
+  uint32 temp_id = 9;
+
+  // A fresh id taking the value I - S * (ctr + 1) at the ctr-th iteration, and
+  // thus I - S * N at the last iteration.
+  uint32 eventual_syn_id = 10;
+
+  // A fresh id for the incremented counter.
+  uint32 incremented_ctr_id = 11;
+
+  // A fresh id for the loop condition.
+  uint32 cond_id = 12;
+
+  // The instructions in the loop can also be laid out in two basic blocks, as follows:
+  //
+  //  %loop_id = OpLabel
+  //   %ctr_id = OpPhi %int %int_0 %pred %incremented_ctr_id %loop_id
+  //  %temp_id = OpPhi %type_of_I %I %pred %eventual_syn_id %loop_id
+  //             OpLoopMerge %block_after_loop_id %additional_block_id None
+  //             OpBranch %additional_block_id
+  //
+  //  %additional_block_id = OpLabel
+  //      %eventual_syn_id = OpISub %type_of_I %temp_id %step_val_id
+  //   %incremented_ctr_id = OpIAdd %int %ctr_id %int_1
+  //              %cond_id = OpSLessThan %bool %incremented_ctr_id %num_iterations_id
+  //                         OpBranchConditional %cond_id %loop_id %block_after_loop_id
+
+  // A fresh id for the additional block. If this is 0, it means that only one
+  // block is to be created.
+  uint32 additional_block_id = 13;
+}
+
 message TransformationAddNoContractionDecoration {
 
   // Applies OpDecorate NoContraction to the given result id
@@ -711,6 +1030,24 @@
 
 }
 
+message TransformationAddOpPhiSynonym {
+
+  // Adds an OpPhi instruction at the start of a block with n predecessors (pred_1, pred_2, ..., pred_n)
+  // and n related ids (id_1, id_2, ..., id_n) which are pairwise synonymous.
+  // The instruction will be of the form:
+  //       %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
+  // and fresh_id will be recorded as being synonymous with all the other ids.
+
+  // Label id of the block
+  uint32 block_id = 1;
+
+  // Pairs (pred_i, id_i)
+  repeated UInt32Pair pred_to_id = 2;
+
+  // Fresh id for the new instruction
+  uint32 fresh_id = 3;
+}
+
 message TransformationAddParameter {
 
   // Adds a new parameter into the function.
@@ -721,15 +1058,17 @@
   // Fresh id for a new parameter.
   uint32 parameter_fresh_id = 2;
 
-  // Result id of the instruction, used to initializer new parameter
-  // in function calls. Type id of that instruction is the type id of the parameter.
-  // It may not be OpTypeVoid.
-  uint32 initializer_id = 3;
+  // Type id for a new parameter.
+  uint32 parameter_type_id = 3;
+
+  // A map that maps from the OpFunctionCall id to the id that will be passed as the new
+  // parameter at that call site. It must have the same type as that of the new parameter.
+  repeated UInt32Pair call_parameter_ids = 4;
 
   // A fresh id for a new function type. This might not be used
   // if a required function type already exists or if we can change
   // the old function type.
-  uint32 function_type_fresh_id = 4;
+  uint32 function_type_fresh_id = 5;
 
 }
 
@@ -986,6 +1325,29 @@
 
 }
 
+message TransformationCompositeInsert {
+
+  // A transformation that adds an instruction OpCompositeInsert which creates
+  // a new composite from an existing composite, with an element inserted.
+
+  // A descriptor for an instruction before which the new instruction
+  // OpCompositeInsert should be inserted.
+  InstructionDescriptor instruction_to_insert_before = 1;
+
+  // Result id of the inserted OpCompositeInsert instruction.
+  uint32 fresh_id = 2;
+
+  // Id of the composite used as the basis for the insertion.
+  uint32 composite_id = 3;
+
+  // Id of the object to be inserted.
+  uint32 object_id = 4;
+
+  // Indices that indicate which part of the composite should be inserted into.
+  repeated uint32 index = 5;
+
+}
+
 message TransformationComputeDataSynonymFactClosure {
 
   // A transformation that impacts the fact manager only, forcing a computation
@@ -999,6 +1361,41 @@
 
 }
 
+message TransformationDuplicateRegionWithSelection {
+
+  // A transformation that inserts a conditional statement with a boolean expression
+  // of arbitrary value and duplicates a given single-entry, single-exit region, so
+  // that it is present in each conditional branch and will be executed regardless
+  // of which branch will be taken.
+
+  // Fresh id for a label of the new entry block.
+  uint32 new_entry_fresh_id = 1;
+
+  // Id for a boolean expression.
+  uint32 condition_id = 2;
+
+  // Fresh id for a label of the merge block of the conditional.
+  uint32 merge_label_fresh_id = 3;
+
+  // Block id of the entry block of the original region.
+  uint32 entry_block_id = 4;
+
+  // Block id of the exit block of the original region.
+  uint32 exit_block_id = 5;
+
+  // Map that maps from a label in the original region to the corresponding label
+  // in the duplicated region.
+  repeated UInt32Pair original_label_to_duplicate_label = 6;
+
+  // Map that maps from a result id in the original region to the corresponding
+  // result id in the duplicated region.
+  repeated UInt32Pair original_id_to_duplicate_id = 7;
+
+  // Map that maps from a result id in the original region to the result id of the
+  // corresponding OpPhi instruction.
+  repeated UInt32Pair original_id_to_phi_id = 8;
+}
+
 message TransformationEquationInstruction {
 
   // A transformation that adds an instruction to the module that defines an
@@ -1021,6 +1418,91 @@
 
 }
 
+message TransformationExpandVectorReduction {
+
+  // A transformation that adds synonyms for OpAny and OpAll instructions by
+  // evaluating each vector component with the corresponding logical operation.
+  // There is a SPIR-V code example in the header file of the transformation
+  // class that can help understand the transformation.
+
+  // The OpAny or OpAll instruction result id.
+  uint32 instruction_result_id = 1;
+
+  // The fresh ids required to apply the transformation.
+  repeated uint32 fresh_ids = 2;
+
+}
+
+message TransformationFlattenConditionalBranch {
+
+  // A transformation that takes a selection construct with a header
+  // containing an OpBranchConditional instruction and flattens it.
+  // For example, something of the form:
+  //
+  // %1 = OpLabel
+  //      [header instructions]
+  //      OpSelectionMerge %4 None
+  //      OpBranchConditional %cond %2 %3
+  // %2 = OpLabel
+  //      [true branch instructions]
+  //      OpBranch %4
+  // %3 = OpLabel
+  //      [false branch instructions]
+  //      OpBranch %4
+  // %4 = OpLabel
+  //      ...
+  //
+  // becomes:
+  //
+  // %1 = OpLabel
+  //      [header instructions]
+  //      OpBranch %2
+  // %2 = OpLabel
+  //      [true branch instructions]
+  //      OpBranch %3
+  // %3 = OpLabel
+  //      [false branch instructions]
+  //      OpBranch %4
+  // %4 = OpLabel
+  //      ...
+  //
+  // If all of the instructions in the true or false branches have
+  // no side effects, this is semantics-preserving.
+  // Side-effecting instructions will instead be enclosed by smaller
+  // conditionals. For more details, look at the definition for the
+  // SideEffectWrapperInfo message.
+  //
+  // Nested conditionals or loops are not supported. The false branch
+  // could also be executed before the true branch, depending on the
+  // |true_branch_first| field.
+
+  // The label id of the header block
+  uint32 header_block_id = 1;
+
+  // A boolean field deciding the order in which the original branches
+  // will be laid out: the true branch will be laid out first iff this
+  // field is true.
+  bool true_branch_first = 2;
+
+  // If the convergence block contains an OpPhi with bvec2 result type, it may
+  // be necessary to introduce a bvec2 with the selection construct's condition
+  // in both components in order to turn the OpPhi into an OpSelect.  This
+  // this field provides a fresh id for an OpCompositeConstruct instruction for
+  // this purpose.  It should be set to 0 if no such instruction is required.
+  uint32 fresh_id_for_bvec2_selector = 3;
+
+  // The same as |fresh_id_for_bvec2_selector| but for the bvec3 case.
+  uint32 fresh_id_for_bvec3_selector = 4;
+
+  // The same as |fresh_id_for_bvec2_selector| but for the bvec4 case.
+  uint32 fresh_id_for_bvec4_selector = 5;
+
+  // A list of instructions with side effects, which must be enclosed
+  // inside smaller conditionals before flattening the main one, and
+  // the corresponding fresh ids and module ids needed.
+  repeated SideEffectWrapperInfo side_effect_wrapper_info = 6;
+}
+
 message TransformationFunctionCall {
 
   // A transformation that introduces an OpFunctionCall instruction.  The call
@@ -1044,6 +1526,20 @@
 
 }
 
+message TransformationInlineFunction {
+
+  // This transformation inlines a function by mapping the function instructions to fresh ids.
+
+  // Result id of the function call instruction.
+  uint32 function_call_id = 1;
+
+  // For each result id defined by the called function,
+  // this map provides an associated fresh id that can
+  // be used in the inlined version of the function call.
+  repeated UInt32Pair result_id_map = 2;
+
+}
+
 message TransformationInvertComparisonOperator {
 
   // For some instruction with result id |operator_id| that
@@ -1076,6 +1572,21 @@
 
 }
 
+message TransformationMakeVectorOperationDynamic {
+
+  // A transformation that replaces the OpCompositeExtract and OpCompositeInsert
+  // instructions with the OpVectorExtractDynamic and OpVectorInsertDynamic instructions.
+
+  // The composite instruction result id.
+  uint32 instruction_result_id = 1;
+
+  // The OpCompositeExtract/Insert instructions accept integer literals as indices to the composite object.
+  // However, the OpVectorInsert/ExtractDynamic instructions require its single index to be an integer instruction.
+  // This is the result id of the integer instruction.
+  uint32 constant_index_id = 2;
+
+}
+
 message TransformationMergeBlocks {
 
   // A transformation that merges a block with its predecessor.
@@ -1086,6 +1597,65 @@
 
 }
 
+message TransformationMergeFunctionReturns {
+
+  // A transformation that modifies a function so that it does not return early,
+  // so it only has one return statement (ignoring unreachable blocks).
+  //
+  // The function is enclosed inside an outer loop, that is only executed once,
+  // and whose merge block is the new return block of the function.
+  //
+  // Each return instruction is replaced by:
+  //     OpBranch %innermost_loop_merge
+  // where %innermost_loop_merge is the innermost loop containing the return
+  // instruction.
+  //
+  // Each merge block whose associated loop contains return instructions is
+  // changed so that it branches to the merge block of the loop containing it,
+  // as explained in the comments to the ReturnMergingInfo message.
+  //
+  // The new return block (the merge block of the new outer loop) will be of
+  // the following form (if the return type is not void):
+  //  %outer_return_id = OpLabel
+  //   %return_val_id = OpPhi %return_type %val1 %block_1 %val2 %block_2 ...
+  //                    OpReturnValue %return_val_id
+  // where %block_k is either a return block that, in the original function, is
+  // outside of any loops, or the merge block of a loop that contains return
+  // instructions and is not, originally, nested inside another loop, and
+  // %block_k is the corresponding return value.
+  // If the function has void type, there will be no OpPhi instruction and the
+  // last instruction will be OpReturn.
+
+  // The id of the function to which the transformation is being applied.
+  uint32 function_id = 1;
+
+  // A fresh id for the header of the new outer loop.
+  uint32 outer_header_id = 2;
+
+  // 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;
+
+  // A fresh id for the value that will be returned.
+  // This is ignored if the function has void return type.
+  uint32 return_val_id = 4;
+
+  // An existing id of the same type as the return value, which is
+  // available to use at the end of the entry block.
+  // This is ignored if the function has void return type or if no
+  // loops in the function contain a return instruction.
+  // If the function is not void, the transformation will add an
+  // OpPhi instruction to each merge block whose associated loop
+  // contains at least a return instruction. The value associated
+  // with existing predecessors from which the function cannot be
+  // returning will be this id, used as a placeholder.
+  uint32 any_returnable_val_id = 5;
+
+  // The information needed to modify the merge blocks of
+  // loops containing return instructions.
+  repeated ReturnMergingInfo return_merging_info = 6;
+}
+
 message TransformationMoveBlockDown {
 
   // A transformation that moves a basic block to be one position lower in
@@ -1095,6 +1665,31 @@
   uint32 block_id = 1;
 }
 
+message TransformationMoveInstructionDown {
+
+  // Swaps |instruction| with the next instruction in the block.
+
+  // The instruction to move down.
+  InstructionDescriptor instruction = 1;
+
+}
+
+message TransformationMutatePointer {
+
+  // Backs up value of the pointer, writes into the pointer and
+  // restores the original value.
+
+  // Result id of the pointer instruction to mutate.
+  uint32 pointer_id = 1;
+
+  // Fresh id for the OpLoad instruction.
+  uint32 fresh_id = 2;
+
+  // Instruction to insert backup, mutation and restoration code before.
+  InstructionDescriptor insert_before = 3;
+
+}
+
 message TransformationOutlineFunction {
 
   // A transformation that outlines a single-entry single-exit region of a
@@ -1183,6 +1778,58 @@
 
 }
 
+message TransformationPropagateInstructionDown {
+
+  // Propagates an instruction from |block_id| into its successors.
+  // Concretely, the transformation clones the propagated instruction
+  // into some of the successors of |block_id| and removes the original
+  // instruction. Additionally, an OpPhi instruction may be added to make sure
+  // that the transformation can be applied in various scenarios.
+  //
+  // Note that the instruction might not be propagated down into every successor
+  // of |block_id| since it might make the module invalid.
+
+  // Id of the block to propagate an instruction from. The decision on what
+  // instruction to propagate is made based on whether the instruction interacts
+  // with memory, whether that instruction is used in its block etc (see the
+  // transformation class for more details).
+  uint32 block_id = 1;
+
+  // A fresh id for an OpPhi instruction. This might not be used by the
+  // transformation since an OpPhi instruction is created only if needed
+  // (e.g. an instruction is propagated into divergent blocks).
+  uint32 phi_fresh_id = 2;
+
+  // A map from the id of some successor of the |block_id| to the fresh id.
+  // The map contains a fresh id for at least every successor of the |block_id|.
+  // Every fresh id in the map corresponds to the result id of the clone,
+  // propagated into the corresponding successor block. This transformation
+  // might use overflow ids if they are available and this field doesn't account
+  // for every successor of |block_id|.
+  repeated UInt32Pair successor_id_to_fresh_id = 3;
+
+}
+
+message TransformationPropagateInstructionUp {
+
+  // Propagates an instruction in the block into the block's predecessors.
+  // Concretely, this transformation clones some particular instruction from
+  // the |block_id| into every block's predecessor and replaces the original
+  // instruction with OpPhi. Take a look at the transformation class to learn
+  // more about how we choose what instruction to propagate.
+
+  // Id of the block to propagate an instruction from.
+  uint32 block_id = 1;
+
+  // A map from the id of some predecessor of the |block_id| to the fresh id.
+  // The map contains a fresh id for at least every predecessor of the |block_id|.
+  // The instruction is propagated by creating a number of clones - one clone for
+  // each predecessor. Fresh ids from this field are used as result ids of cloned
+  // instructions.
+  repeated UInt32Pair predecessor_id_to_fresh_id = 2;
+
+}
+
 message TransformationPushIdThroughVariable {
 
   // A transformation that makes |value_synonym_id| and |value_id| to be
@@ -1230,6 +1877,42 @@
 
 }
 
+message TransformationReplaceAddSubMulWithCarryingExtended {
+
+  // Replaces OpIAdd with OpIAddCarry, OpISub with OpISubBorrow, OpIMul
+  // with OpUMulExtended or OpSMulExtended (depending on the signedness
+  // of the operands) and stores the result into a |struct_fresh_id|.
+  // In the original instruction the result type id and the type ids of
+  // the operands must be the same. Then the transformation extracts
+  // the first element of the result into the original |result_id|.
+  // This value is the same as the result of the original instruction.
+
+  // The fresh id of the intermediate result.
+  uint32 struct_fresh_id = 1;
+
+  // The result id of the original instruction.
+  uint32 result_id = 2;
+
+}
+
+message TransformationReplaceBranchFromDeadBlockWithExit {
+
+  // Given a dead block that ends with OpBranch, replaces OpBranch with an
+  // "exit" instruction; one of OpReturn/OpReturnValue, OpKill (in a fragment
+  // shader) or OpUnreachable.
+
+  // The dead block whose terminator is to be replaced.
+  uint32 block_id = 1;
+
+  // The opcode of the new terminator.
+  uint32 opcode = 2;
+
+  // Ignored unless opcode is OpReturnValue, in which case this field provides
+  // a suitable result id to be returned.
+  uint32 return_value_id = 3;
+
+}
+
 message TransformationReplaceParameterWithGlobal {
 
   // Removes parameter with result id |parameter_id| from its function
@@ -1290,16 +1973,16 @@
 
 message TransformationReplaceCopyMemoryWithLoadStore {
 
-   // A transformation that replaces instructions OpCopyMemory with loading
-   // the source variable to an intermediate value and storing this value into the
-   // target variable of the original OpCopyMemory instruction.
+  // A transformation that replaces instructions OpCopyMemory with loading
+  // the source variable to an intermediate value and storing this value into the
+  // target variable of the original OpCopyMemory instruction.
 
-   // The intermediate value.
-   uint32 fresh_id = 1;
+  // The intermediate value.
+  uint32 fresh_id = 1;
 
-   // The instruction descriptor to OpCopyMemory. It is necessary, because
-   // OpCopyMemory doesn't have a result id.
-   InstructionDescriptor copy_memory_instruction_descriptor = 2;
+  // The instruction descriptor to OpCopyMemory. It is necessary, because
+  // OpCopyMemory doesn't have a result id.
+  InstructionDescriptor copy_memory_instruction_descriptor = 2;
 }
 
 message TransformationReplaceCopyObjectWithStoreLoad {
@@ -1334,6 +2017,17 @@
 
 }
 
+message TransformationReplaceIrrelevantId {
+
+  // Replaces an irrelevant id with another id of the same type.
+
+  // The id use that is to be replaced
+  IdUseDescriptor id_use_descriptor = 1;
+
+  // The replacement id
+  uint32 replacement_id = 2;
+}
+
 message TransformationReplaceLinearAlgebraInstruction {
 
   // Replaces a linear algebra instruction with its
@@ -1343,20 +2037,6 @@
   repeated uint32 fresh_ids = 1;
 
   // A descriptor for a linear algebra instruction.
-  // This transformation is only applicable if the described instruction has one of the following opcodes.
-  // Supported:
-  //   OpVectorTimesScalar
-  //   OpMatrixTimesScalar
-  //   OpVectorTimesMatrix
-  //   OpMatrixTimesVector
-  //   OpMatrixTimesMatrix
-  //   OpDot
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354):
-  // Right now we only support certain operations. When this issue is addressed
-  // the supporting comments can be removed.
-  // To be supported in the future:
-  //   OpTranspose
-  //   OpOuterProduct
   InstructionDescriptor instruction_descriptor = 2;
 
 }
@@ -1373,6 +2053,59 @@
   InstructionDescriptor store_instruction_descriptor = 2;
 }
 
+message TransformationReplaceOpPhiIdFromDeadPredecessor {
+
+  // Replaces one of the ids used by an OpPhi instruction, when
+  // the corresponding predecessor is dead, with any available id
+  // of the correct type.
+
+  // The result id of the OpPhi instruction.
+  uint32 opphi_id = 1;
+
+  // The label id of one of the predecessors of the block containing
+  // the OpPhi instruction, corresponding to the id that we want to
+  // replace.
+  uint32 pred_label_id = 2;
+
+  // The id that, after the transformation, will be associated with
+  // the given predecessor.
+  uint32 replacement_id = 3;
+
+}
+
+message TransformationReplaceOpSelectWithConditionalBranch {
+
+  // A transformation that takes an OpSelect instruction with a
+  // scalar boolean condition and replaces it with a conditional
+  // branch and an OpPhi instruction.
+  // The OpSelect instruction must be the first instruction in its
+  // block, which must have a unique predecessor. The block will
+  // become the merge block of a new construct, while its predecessor
+  // will become the header.
+  // Given the original OpSelect instruction:
+  //   %id = OpSelect %type %cond %then %else
+  // The branching instruction of the header will be:
+  //         OpBranchConditional %cond %true_block_id %false_block_id
+  // and the OpSelect instruction will be turned into:
+  //   %id = OpPhi %type %then %true_block_id %else %false_block_id
+  // At most one of |true_block_id| and |false_block_id| can be zero. In
+  // that case, there will be no such block and all references to it
+  // will be replaced by %merge_block (where %merge_block is the
+  // block containing the OpSelect instruction).
+
+  // The result id of the OpSelect instruction.
+  uint32 select_id = 1;
+
+  // A fresh id for the new block that the predecessor of the block
+  // containing |select_id| will branch to if the condition holds.
+  uint32 true_block_id = 2;
+
+  // A fresh id for the new block that the predecessor of the block
+  // containing |select_id| will branch to if the condition does not
+  // hold.
+  uint32 false_block_id = 3;
+}
+
 message TransformationReplaceParamsWithStruct {
 
   // Replaces parameters of the function with a struct containing
@@ -1389,12 +2122,9 @@
   uint32 fresh_parameter_id = 3;
 
   // Fresh ids for struct objects containing values of replaced parameters.
-  // This map contains a fresh id for at least every result id of a relevant
+  // This field contains a fresh id for at least every result id of a relevant
   // OpFunctionCall instruction.
-  //
-  // While maps are not fully deterministic, the way this map is used does not
-  // exhibit nondeterminism. Change to repeated Uint32Pair if this changes.
-  map<uint32, uint32> caller_id_to_fresh_composite_id = 4;
+  repeated UInt32Pair caller_id_to_fresh_composite_id = 4;
 
 }
 
@@ -1560,3 +2290,53 @@
   repeated uint32 component = 5;
 
 }
+
+message TransformationWrapEarlyTerminatorInFunction {
+
+  // Replaces an early terminator - OpKill, OpReachable or OpTerminateInvocation
+  // - with a call to a wrapper function for the terminator.
+
+  // A fresh id for a new OpFunctionCall instruction.
+  uint32 fresh_id = 1;
+
+  // A descriptor for an OpKill, OpUnreachable or OpTerminateInvocation
+  // instruction.
+  InstructionDescriptor early_terminator_instruction = 2;
+
+  // An id with the same type as the enclosing function's return type that is
+  // available at the early terminator.  This is used to change the terminator
+  // to OpReturnValue.  Ignored if the enclosing function has void return type,
+  // in which case OpReturn can be used as the new terminator.
+  uint32 returned_value_id = 3;
+
+}
+
+message TransformationWrapRegionInSelection {
+
+  // Transforms a single-entry-single-exit region R into
+  // if (|branch_condition|) { R } else { R }
+  // The entry block for R becomes a selection header and
+  // the exit block - a selection merge.
+  //
+  // Note that the region R is not duplicated. Thus, the effect of
+  // this transformation can be represented as follows:
+  //              entry
+  //  entry        / \
+  //    |          \ /
+  //    R   -->     R
+  //    |           |
+  //   exit        exit
+
+  // This behaviour is different from TransformationDuplicateRegionWithSelection
+  // that copies the blocks in R.
+
+  // The entry block for the region R.
+  uint32 region_entry_block_id = 1;
+
+  // The exit block for the region R.
+  uint32 region_exit_block_id = 2;
+
+  // Boolean value for the condition expression.
+  bool branch_condition = 3;
+
+}
diff --git a/source/fuzz/pseudo_random_generator.cpp b/source/fuzz/pseudo_random_generator.cpp
index 9643264..51f0538 100644
--- a/source/fuzz/pseudo_random_generator.cpp
+++ b/source/fuzz/pseudo_random_generator.cpp
@@ -25,8 +25,12 @@
 
 uint32_t PseudoRandomGenerator::RandomUint32(uint32_t bound) {
   assert(bound > 0 && "Bound must be positive");
-  return static_cast<uint32_t>(
-      std::uniform_int_distribution<>(0, bound - 1)(mt_));
+  return std::uniform_int_distribution<uint32_t>(0, bound - 1)(mt_);
+}
+
+uint64_t PseudoRandomGenerator::RandomUint64(uint64_t bound) {
+  assert(bound > 0 && "Bound must be positive");
+  return std::uniform_int_distribution<uint64_t>(0, bound - 1)(mt_);
 }
 
 bool PseudoRandomGenerator::RandomBool() {
diff --git a/source/fuzz/pseudo_random_generator.h b/source/fuzz/pseudo_random_generator.h
index d2f5292..7c19833 100644
--- a/source/fuzz/pseudo_random_generator.h
+++ b/source/fuzz/pseudo_random_generator.h
@@ -31,6 +31,8 @@
 
   uint32_t RandomUint32(uint32_t bound) override;
 
+  uint64_t RandomUint64(uint64_t bound) override;
+
   uint32_t RandomPercentage() override;
 
   bool RandomBool() override;
diff --git a/source/fuzz/random_generator.h b/source/fuzz/random_generator.h
index 9c46798..8c1baaa 100644
--- a/source/fuzz/random_generator.h
+++ b/source/fuzz/random_generator.h
@@ -29,6 +29,9 @@
   // Returns a value in the half-open interval [0, bound).
   virtual uint32_t RandomUint32(uint32_t bound) = 0;
 
+  // Returns a value in the half-open interval [0, bound).
+  virtual uint64_t RandomUint64(uint64_t bound) = 0;
+
   // Returns a value in the closed interval [0, 100].
   virtual uint32_t RandomPercentage() = 0;
 
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
index bc680d8..5b3468e 100644
--- a/source/fuzz/replayer.cpp
+++ b/source/fuzz/replayer.cpp
@@ -14,107 +14,114 @@
 
 #include "source/fuzz/replayer.h"
 
+#include <algorithm>
+#include <memory>
 #include <utility>
 
-#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/counter_overflow_id_source.h"
+#include "source/fuzz/fact_manager/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
-#include "source/fuzz/transformation_add_constant_boolean.h"
-#include "source/fuzz/transformation_add_constant_scalar.h"
-#include "source/fuzz/transformation_add_dead_break.h"
-#include "source/fuzz/transformation_add_type_boolean.h"
-#include "source/fuzz/transformation_add_type_float.h"
-#include "source/fuzz/transformation_add_type_int.h"
-#include "source/fuzz/transformation_add_type_pointer.h"
 #include "source/fuzz/transformation_context.h"
-#include "source/fuzz/transformation_move_block_down.h"
-#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
-#include "source/fuzz/transformation_replace_constant_with_uniform.h"
-#include "source/fuzz/transformation_split_block.h"
 #include "source/opt/build_module.h"
 #include "source/util/make_unique.h"
 
 namespace spvtools {
 namespace fuzz {
 
-struct Replayer::Impl {
-  Impl(spv_target_env env, bool validate, spv_validator_options options)
-      : target_env(env),
-        validate_during_replay(validate),
-        validator_options(options) {}
-
-  const spv_target_env target_env;    // Target environment.
-  MessageConsumer consumer;           // Message consumer.
-  const bool validate_during_replay;  // Controls whether the validator should
-                                      // be run after every replay step.
-  spv_validator_options validator_options;  // Options to control
-                                            // validation
-};
-
-Replayer::Replayer(spv_target_env env, bool validate_during_replay,
-                   spv_validator_options validator_options)
-    : impl_(MakeUnique<Impl>(env, validate_during_replay, validator_options)) {}
-
-Replayer::~Replayer() = default;
-
-void Replayer::SetMessageConsumer(MessageConsumer c) {
-  impl_->consumer = std::move(c);
-}
-
-Replayer::ReplayerResultStatus Replayer::Run(
+Replayer::Replayer(
+    spv_target_env target_env, MessageConsumer consumer,
     const std::vector<uint32_t>& binary_in,
     const protobufs::FactSequence& initial_facts,
     const protobufs::TransformationSequence& transformation_sequence_in,
-    uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out,
-    protobufs::TransformationSequence* transformation_sequence_out) const {
+    uint32_t num_transformations_to_apply, bool validate_during_replay,
+    spv_validator_options validator_options)
+    : target_env_(target_env),
+      consumer_(std::move(consumer)),
+      binary_in_(binary_in),
+      initial_facts_(initial_facts),
+      transformation_sequence_in_(transformation_sequence_in),
+      num_transformations_to_apply_(num_transformations_to_apply),
+      validate_during_replay_(validate_during_replay),
+      validator_options_(validator_options) {}
+
+Replayer::~Replayer() = default;
+
+Replayer::ReplayerResult Replayer::Run() {
   // Check compatibility between the library version being linked with and the
   // header files being used.
   GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-  if (num_transformations_to_apply >
-      static_cast<uint32_t>(transformation_sequence_in.transformation_size())) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "The number of transformations to be replayed must not "
-                    "exceed the size of the transformation sequence.");
-    return Replayer::ReplayerResultStatus::kTooManyTransformationsRequested;
+  if (num_transformations_to_apply_ >
+      static_cast<uint32_t>(
+          transformation_sequence_in_.transformation_size())) {
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "The number of transformations to be replayed must not "
+              "exceed the size of the transformation sequence.");
+    return {Replayer::ReplayerResultStatus::kTooManyTransformationsRequested,
+            nullptr, nullptr, protobufs::TransformationSequence()};
   }
 
-  spvtools::SpirvTools tools(impl_->target_env);
+  spvtools::SpirvTools tools(target_env_);
   if (!tools.IsValid()) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Failed to create SPIRV-Tools interface; stopping.");
-    return Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface;
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "Failed to create SPIRV-Tools interface; stopping.");
+    return {Replayer::ReplayerResultStatus::kFailedToCreateSpirvToolsInterface,
+            nullptr, nullptr, protobufs::TransformationSequence()};
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size(),
-                      impl_->validator_options)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial binary is invalid; stopping.");
-    return Replayer::ReplayerResultStatus::kInitialBinaryInvalid;
+  if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) {
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial binary is invalid; stopping.");
+    return {Replayer::ReplayerResultStatus::kInitialBinaryInvalid, nullptr,
+            nullptr, protobufs::TransformationSequence()};
   }
 
   // Build the module from the input binary.
-  std::unique_ptr<opt::IRContext> ir_context = BuildModule(
-      impl_->target_env, impl_->consumer, binary_in.data(), binary_in.size());
+  std::unique_ptr<opt::IRContext> ir_context =
+      BuildModule(target_env_, consumer_, binary_in_.data(), binary_in_.size());
   assert(ir_context);
 
   // For replay validation, we track the last valid SPIR-V binary that was
   // observed. Initially this is the input binary.
   std::vector<uint32_t> last_valid_binary;
-  if (impl_->validate_during_replay) {
-    last_valid_binary = binary_in;
+  if (validate_during_replay_) {
+    last_valid_binary = binary_in_;
   }
 
-  FactManager fact_manager;
-  fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
-  TransformationContext transformation_context(&fact_manager,
-                                               impl_->validator_options);
+  // We find the smallest id that is (a) not in use by the original module, and
+  // (b) not used by any transformation in the sequence to be replayed.  This
+  // serves as a starting id from which to issue overflow ids if they are
+  // required during replay.
+  uint32_t first_overflow_id = ir_context->module()->id_bound();
+  for (auto& transformation : transformation_sequence_in_.transformation()) {
+    auto fresh_ids = Transformation::FromMessage(transformation)->GetFreshIds();
+    if (!fresh_ids.empty()) {
+      first_overflow_id =
+          std::max(first_overflow_id,
+                   *std::max_element(fresh_ids.begin(), fresh_ids.end()) + 1);
+    }
+  }
+
+  std::unique_ptr<TransformationContext> transformation_context =
+      MakeUnique<TransformationContext>(
+          MakeUnique<FactManager>(ir_context.get()), validator_options_,
+          MakeUnique<CounterOverflowIdSource>(first_overflow_id));
+  transformation_context->GetFactManager()->AddInitialFacts(consumer_,
+                                                            initial_facts_);
+
+  // We track the largest id bound observed, to ensure that it only increases
+  // as transformations are applied.
+  uint32_t max_observed_id_bound = ir_context->module()->id_bound();
+  (void)(max_observed_id_bound);  // Keep release-mode compilers happy.
+
+  protobufs::TransformationSequence transformation_sequence_out;
 
   // Consider the transformation proto messages in turn.
   uint32_t counter = 0;
-  for (auto& message : transformation_sequence_in.transformation()) {
-    if (counter >= num_transformations_to_apply) {
+  for (auto& message : transformation_sequence_in_.transformation()) {
+    if (counter >= num_transformations_to_apply_) {
       break;
     }
     counter++;
@@ -123,23 +130,29 @@
 
     // Check whether the transformation can be applied.
     if (transformation->IsApplicable(ir_context.get(),
-                                     transformation_context)) {
+                                     *transformation_context)) {
       // The transformation is applicable, so apply it, and copy it to the
       // sequence of transformations that were applied.
-      transformation->Apply(ir_context.get(), &transformation_context);
-      *transformation_sequence_out->add_transformation() = message;
+      transformation->Apply(ir_context.get(), transformation_context.get());
+      *transformation_sequence_out.add_transformation() = message;
 
-      if (impl_->validate_during_replay) {
+      assert(ir_context->module()->id_bound() >= max_observed_id_bound &&
+             "The module's id bound should only increase due to applying "
+             "transformations.");
+      max_observed_id_bound = ir_context->module()->id_bound();
+
+      if (validate_during_replay_) {
         std::vector<uint32_t> binary_to_validate;
         ir_context->module()->ToBinary(&binary_to_validate, false);
 
         // Check whether the latest transformation led to a valid binary.
         if (!tools.Validate(&binary_to_validate[0], binary_to_validate.size(),
-                            impl_->validator_options)) {
-          impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                          "Binary became invalid during replay (set a "
-                          "breakpoint to inspect); stopping.");
-          return Replayer::ReplayerResultStatus::kReplayValidationFailure;
+                            validator_options_)) {
+          consumer_(SPV_MSG_INFO, nullptr, {},
+                    "Binary became invalid during replay (set a "
+                    "breakpoint to inspect); stopping.");
+          return {Replayer::ReplayerResultStatus::kReplayValidationFailure,
+                  nullptr, nullptr, protobufs::TransformationSequence()};
         }
 
         // The binary was valid, so it becomes the latest valid binary.
@@ -148,9 +161,9 @@
     }
   }
 
-  // Write out the module as a binary.
-  ir_context->module()->ToBinary(binary_out, false);
-  return Replayer::ReplayerResultStatus::kComplete;
+  return {Replayer::ReplayerResultStatus::kComplete, std::move(ir_context),
+          std::move(transformation_context),
+          std::move(transformation_sequence_out)};
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h
index d6395aa..6730bd1 100644
--- a/source/fuzz/replayer.h
+++ b/source/fuzz/replayer.h
@@ -19,6 +19,8 @@
 #include <vector>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -29,7 +31,7 @@
 class Replayer {
  public:
   // Possible statuses that can result from running the replayer.
-  enum ReplayerResultStatus {
+  enum class ReplayerResultStatus {
     kComplete,
     kFailedToCreateSpirvToolsInterface,
     kInitialBinaryInvalid,
@@ -37,8 +39,18 @@
     kTooManyTransformationsRequested,
   };
 
-  // Constructs a replayer from the given target environment.
-  Replayer(spv_target_env env, bool validate_during_replay,
+  struct ReplayerResult {
+    ReplayerResultStatus status;
+    std::unique_ptr<opt::IRContext> transformed_module;
+    std::unique_ptr<TransformationContext> transformation_context;
+    protobufs::TransformationSequence applied_transformations;
+  };
+
+  Replayer(spv_target_env target_env, MessageConsumer consumer,
+           const std::vector<uint32_t>& binary_in,
+           const protobufs::FactSequence& initial_facts,
+           const protobufs::TransformationSequence& transformation_sequence_in,
+           uint32_t num_transformations_to_apply, bool validate_during_replay,
            spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
@@ -49,26 +61,42 @@
 
   ~Replayer();
 
-  // Sets the message consumer to the given |consumer|. The |consumer| will be
-  // invoked once for each message communicated from the library.
-  void SetMessageConsumer(MessageConsumer consumer);
-
-  // Transforms |binary_in| to |binary_out| by attempting to apply the first
-  // |num_transformations_to_apply| transformations from
-  // |transformation_sequence_in|.  Initial facts about the input binary and the
-  // context in which it will execute are provided via |initial_facts|.  The
-  // transformations that were successfully applied are returned via
-  // |transformation_sequence_out|.
-  ReplayerResultStatus Run(
-      const std::vector<uint32_t>& binary_in,
-      const protobufs::FactSequence& initial_facts,
-      const protobufs::TransformationSequence& transformation_sequence_in,
-      uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out,
-      protobufs::TransformationSequence* transformation_sequence_out) const;
+  // Attempts to apply the first |num_transformations_to_apply_| transformations
+  // from |transformation_sequence_in_| to |binary_in_|.  Initial facts about
+  // the input binary and the context in which it will execute are provided via
+  // |initial_facts_|.
+  //
+  // On success, returns a successful result status together with the
+  // transformations that were applied, the IR for the transformed module, and
+  // the transformation context that arises from applying these transformations.
+  // Otherwise, returns an appropriate result status, an empty transformation
+  // sequence, and null pointers for the IR context and transformation context.
+  ReplayerResult Run();
 
  private:
-  struct Impl;                  // Opaque struct for holding internal data.
-  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+  // Target environment.
+  const spv_target_env target_env_;
+
+  // Message consumer.
+  MessageConsumer consumer_;
+
+  // The binary to which transformations are to 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_;
+
+  // The transformations to be replayed.
+  const protobufs::TransformationSequence& transformation_sequence_in_;
+
+  // The number of transformations that should be replayed.
+  const uint32_t num_transformations_to_apply_;
+
+  // Controls whether the validator should be run after every replay step.
+  const bool validate_during_replay_;
+
+  // Options to control validation
+  spv_validator_options validator_options_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
index 829df63..c1e2514 100644
--- a/source/fuzz/shrinker.cpp
+++ b/source/fuzz/shrinker.cpp
@@ -16,8 +16,11 @@
 
 #include <sstream>
 
+#include "source/fuzz/added_function_reducer.h"
 #include "source/fuzz/pseudo_random_generator.h"
 #include "source/fuzz/replayer.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
 #include "source/spirv_fuzzer_options.h"
 #include "source/util/make_unique.h"
 
@@ -59,85 +62,77 @@
 
 }  // namespace
 
-struct Shrinker::Impl {
-  Impl(spv_target_env env, uint32_t limit, bool validate,
-       spv_validator_options options)
-      : target_env(env),
-        step_limit(limit),
-        validate_during_replay(validate),
-        validator_options(options) {}
-
-  const spv_target_env target_env;          // Target environment.
-  MessageConsumer consumer;                 // Message consumer.
-  const uint32_t step_limit;                // Step limit for reductions.
-  const bool validate_during_replay;        // Determines whether to check for
-                                            // validity during the replaying of
-                                            // transformations.
-  spv_validator_options validator_options;  // Options to control validation.
-};
-
-Shrinker::Shrinker(spv_target_env env, uint32_t step_limit,
-                   bool validate_during_replay,
-                   spv_validator_options validator_options)
-    : impl_(MakeUnique<Impl>(env, step_limit, validate_during_replay,
-                             validator_options)) {}
-
-Shrinker::~Shrinker() = default;
-
-void Shrinker::SetMessageConsumer(MessageConsumer c) {
-  impl_->consumer = std::move(c);
-}
-
-Shrinker::ShrinkerResultStatus Shrinker::Run(
+Shrinker::Shrinker(
+    spv_target_env target_env, MessageConsumer consumer,
     const std::vector<uint32_t>& binary_in,
     const protobufs::FactSequence& initial_facts,
     const protobufs::TransformationSequence& transformation_sequence_in,
-    const Shrinker::InterestingnessFunction& interestingness_function,
-    std::vector<uint32_t>* binary_out,
-    protobufs::TransformationSequence* transformation_sequence_out) const {
+    const InterestingnessFunction& interestingness_function,
+    uint32_t step_limit, bool validate_during_replay,
+    spv_validator_options validator_options)
+    : target_env_(target_env),
+      consumer_(std::move(consumer)),
+      binary_in_(binary_in),
+      initial_facts_(initial_facts),
+      transformation_sequence_in_(transformation_sequence_in),
+      interestingness_function_(interestingness_function),
+      step_limit_(step_limit),
+      validate_during_replay_(validate_during_replay),
+      validator_options_(validator_options) {}
+
+Shrinker::~Shrinker() = default;
+
+Shrinker::ShrinkerResult Shrinker::Run() {
   // Check compatibility between the library version being linked with and the
   // header files being used.
   GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-  spvtools::SpirvTools tools(impl_->target_env);
+  SpirvTools tools(target_env_);
   if (!tools.IsValid()) {
-    impl_->consumer(SPV_MSG_ERROR, nullptr, {},
-                    "Failed to create SPIRV-Tools interface; stopping.");
-    return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface;
+    consumer_(SPV_MSG_ERROR, nullptr, {},
+              "Failed to create SPIRV-Tools interface; stopping.");
+    return {Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface,
+            std::vector<uint32_t>(), protobufs::TransformationSequence()};
   }
 
   // Initial binary should be valid.
-  if (!tools.Validate(&binary_in[0], binary_in.size(),
-                      impl_->validator_options)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial binary is invalid; stopping.");
-    return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid;
+  if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) {
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial binary is invalid; stopping.");
+    return {Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid,
+            std::vector<uint32_t>(), protobufs::TransformationSequence()};
   }
 
-  std::vector<uint32_t> current_best_binary;
-  protobufs::TransformationSequence current_best_transformations;
-
-  // Run a replay of the initial transformation sequence to (a) check that it
-  // succeeds, (b) get the binary that results from running these
-  // transformations, and (c) get the subsequence of the initial transformations
-  // that actually apply (in principle this could be a strict subsequence).
-  Replayer replayer(impl_->target_env, impl_->validate_during_replay,
-                    impl_->validator_options);
-  replayer.SetMessageConsumer(impl_->consumer);
-  if (replayer.Run(binary_in, initial_facts, transformation_sequence_in,
-                   static_cast<uint32_t>(
-                       transformation_sequence_in.transformation_size()),
-                   &current_best_binary, &current_best_transformations) !=
+  // Run a replay of the initial transformation sequence to check that it
+  // succeeds.
+  auto initial_replay_result =
+      Replayer(target_env_, consumer_, binary_in_, initial_facts_,
+               transformation_sequence_in_,
+               static_cast<uint32_t>(
+                   transformation_sequence_in_.transformation_size()),
+               validate_during_replay_, validator_options_)
+          .Run();
+  if (initial_replay_result.status !=
       Replayer::ReplayerResultStatus::kComplete) {
-    return ShrinkerResultStatus::kReplayFailed;
+    return {ShrinkerResultStatus::kReplayFailed, std::vector<uint32_t>(),
+            protobufs::TransformationSequence()};
   }
+  // Get the binary that results from running these transformations, and the
+  // subsequence of the initial transformations that actually apply (in
+  // principle this could be a strict subsequence).
+  std::vector<uint32_t> current_best_binary;
+  initial_replay_result.transformed_module->module()->ToBinary(
+      &current_best_binary, false);
+  protobufs::TransformationSequence current_best_transformations =
+      std::move(initial_replay_result.applied_transformations);
 
   // Check that the binary produced by applying the initial transformations is
   // indeed interesting.
-  if (!interestingness_function(current_best_binary, 0)) {
-    impl_->consumer(SPV_MSG_INFO, nullptr, {},
-                    "Initial binary is not interesting; stopping.");
-    return ShrinkerResultStatus::kInitialBinaryNotInteresting;
+  if (!interestingness_function_(current_best_binary, 0)) {
+    consumer_(SPV_MSG_INFO, nullptr, {},
+              "Initial binary is not interesting; stopping.");
+    return {ShrinkerResultStatus::kInitialBinaryNotInteresting,
+            std::vector<uint32_t>(), protobufs::TransformationSequence()};
   }
 
   uint32_t attempt = 0;  // Keeps track of the number of shrink attempts that
@@ -153,7 +148,7 @@
   // - reach the step limit,
   // - run out of transformations to remove, or
   // - cannot make the chunk size any smaller.
-  while (attempt < impl_->step_limit &&
+  while (attempt < step_limit_ &&
          !current_best_transformations.transformation().empty() &&
          chunk_size > 0) {
     bool progress_this_round =
@@ -183,7 +178,7 @@
     // |chunk_size|, using |chunk_index| to track which chunk to try removing
     // next.  The loop exits early if we reach the shrinking step limit.
     for (int chunk_index = num_chunks - 1;
-         attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) {
+         attempt < step_limit_ && chunk_index >= 0; chunk_index--) {
       // Remove a chunk of transformations according to the current index and
       // chunk size.
       auto transformations_with_chunk_removed =
@@ -195,29 +190,36 @@
       // replay might be even smaller than the transformations with the chunk
       // removed, because removing those transformations might make further
       // transformations inapplicable.
-      std::vector<uint32_t> next_binary;
-      protobufs::TransformationSequence next_transformation_sequence;
-      if (replayer.Run(
-              binary_in, initial_facts, transformations_with_chunk_removed,
+      auto replay_result =
+          Replayer(
+              target_env_, consumer_, binary_in_, initial_facts_,
+              transformations_with_chunk_removed,
               static_cast<uint32_t>(
                   transformations_with_chunk_removed.transformation_size()),
-              &next_binary, &next_transformation_sequence) !=
-          Replayer::ReplayerResultStatus::kComplete) {
+              validate_during_replay_, validator_options_)
+              .Run();
+      if (replay_result.status != Replayer::ReplayerResultStatus::kComplete) {
         // Replay should not fail; if it does, we need to abort shrinking.
-        return ShrinkerResultStatus::kReplayFailed;
+        return {ShrinkerResultStatus::kReplayFailed, std::vector<uint32_t>(),
+                protobufs::TransformationSequence()};
       }
 
-      assert(NumRemainingTransformations(next_transformation_sequence) >=
-                 chunk_index * chunk_size &&
-             "Removing this chunk of transformations should not have an effect "
-             "on earlier chunks.");
+      assert(
+          NumRemainingTransformations(replay_result.applied_transformations) >=
+              chunk_index * chunk_size &&
+          "Removing this chunk of transformations should not have an effect "
+          "on earlier chunks.");
 
-      if (interestingness_function(next_binary, attempt)) {
+      std::vector<uint32_t> transformed_binary;
+      replay_result.transformed_module->module()->ToBinary(&transformed_binary,
+                                                           false);
+      if (interestingness_function_(transformed_binary, attempt)) {
         // If the binary arising from the smaller transformation sequence is
         // interesting, this becomes our current best binary and transformation
         // sequence.
-        current_best_binary = next_binary;
-        current_best_transformations = next_transformation_sequence;
+        current_best_binary = std::move(transformed_binary);
+        current_best_transformations =
+            std::move(replay_result.applied_transformations);
         progress_this_round = true;
       }
       // Either way, this was a shrink attempt, so increment our count of shrink
@@ -237,22 +239,77 @@
     }
   }
 
-  // The output from the shrinker is the best binary we saw, and the
-  // transformations that led to it.
-  *binary_out = current_best_binary;
-  *transformation_sequence_out = current_best_transformations;
+  // We now use spirv-reduce to minimise the functions associated with any
+  // AddFunction transformations that remain.
+  //
+  // Consider every remaining transformation.
+  for (uint32_t transformation_index = 0;
+       attempt < step_limit_ &&
+       transformation_index <
+           static_cast<uint32_t>(
+               current_best_transformations.transformation_size());
+       transformation_index++) {
+    // Skip all transformations apart from TransformationAddFunction.
+    if (!current_best_transformations.transformation(transformation_index)
+             .has_add_function()) {
+      continue;
+    }
+    // Invoke spirv-reduce on the function encoded in this AddFunction
+    // transformation.  The details of this are rather involved, and so are
+    // encapsulated in a separate class.
+    auto added_function_reducer_result =
+        AddedFunctionReducer(target_env_, consumer_, binary_in_, initial_facts_,
+                             current_best_transformations, transformation_index,
+                             interestingness_function_, validate_during_replay_,
+                             validator_options_, step_limit_, attempt)
+            .Run();
+    // Reducing the added function should succeed.  If it doesn't, we report
+    // a shrinking error.
+    if (added_function_reducer_result.status !=
+        AddedFunctionReducer::AddedFunctionReducerResultStatus::kComplete) {
+      return {ShrinkerResultStatus::kAddedFunctionReductionFailed,
+              std::vector<uint32_t>(), protobufs::TransformationSequence()};
+    }
+    assert(current_best_transformations.transformation_size() ==
+               added_function_reducer_result.applied_transformations
+                   .transformation_size() &&
+           "The number of transformations should not have changed.");
+    current_best_binary =
+        std::move(added_function_reducer_result.transformed_binary);
+    current_best_transformations =
+        std::move(added_function_reducer_result.applied_transformations);
+    // The added function reducer reports how many reduction attempts
+    // spirv-reduce took when reducing the function.  We regard each of these
+    // as a shrinker attempt.
+    attempt += added_function_reducer_result.num_reduction_attempts;
+  }
 
   // Indicate whether shrinking completed or was truncated due to reaching the
   // step limit.
-  assert(attempt <= impl_->step_limit);
-  if (attempt == impl_->step_limit) {
+  //
+  // Either way, the output from the shrinker is the best binary we saw, and the
+  // transformations that led to it.
+  assert(attempt <= step_limit_);
+  if (attempt == step_limit_) {
     std::stringstream strstream;
-    strstream << "Shrinking did not complete; step limit " << impl_->step_limit
+    strstream << "Shrinking did not complete; step limit " << step_limit_
               << " was reached.";
-    impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
-    return Shrinker::ShrinkerResultStatus::kStepLimitReached;
+    consumer_(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
+    return {Shrinker::ShrinkerResultStatus::kStepLimitReached,
+            std::move(current_best_binary),
+            std::move(current_best_transformations)};
   }
-  return Shrinker::ShrinkerResultStatus::kComplete;
+  return {Shrinker::ShrinkerResultStatus::kComplete,
+          std::move(current_best_binary),
+          std::move(current_best_transformations)};
+}
+
+uint32_t Shrinker::GetIdBound(const std::vector<uint32_t>& binary) const {
+  // Build the module from the input binary.
+  std::unique_ptr<opt::IRContext> ir_context =
+      BuildModule(target_env_, consumer_, binary.data(), binary.size());
+  assert(ir_context && "Error building module.");
+  return ir_context->module()->id_bound();
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/shrinker.h b/source/fuzz/shrinker.h
index 17b15bf..b9015d6 100644
--- a/source/fuzz/shrinker.h
+++ b/source/fuzz/shrinker.h
@@ -30,13 +30,20 @@
 class Shrinker {
  public:
   // Possible statuses that can result from running the shrinker.
-  enum ShrinkerResultStatus {
+  enum class ShrinkerResultStatus {
     kComplete,
     kFailedToCreateSpirvToolsInterface,
     kInitialBinaryInvalid,
     kInitialBinaryNotInteresting,
     kReplayFailed,
     kStepLimitReached,
+    kAddedFunctionReductionFailed,
+  };
+
+  struct ShrinkerResult {
+    ShrinkerResultStatus status;
+    std::vector<uint32_t> transformed_binary;
+    protobufs::TransformationSequence applied_transformations;
   };
 
   // The type for a function that will take a binary, |binary|, and return true
@@ -49,8 +56,12 @@
   using InterestingnessFunction = std::function<bool(
       const std::vector<uint32_t>& binary, uint32_t counter)>;
 
-  // Constructs a shrinker from the given target environment.
-  Shrinker(spv_target_env env, uint32_t step_limit, bool validate_during_replay,
+  Shrinker(spv_target_env target_env, MessageConsumer consumer,
+           const std::vector<uint32_t>& binary_in,
+           const protobufs::FactSequence& initial_facts,
+           const protobufs::TransformationSequence& transformation_sequence_in,
+           const InterestingnessFunction& interestingness_function,
+           uint32_t step_limit, bool validate_during_replay,
            spv_validator_options validator_options);
 
   // Disables copy/move constructor/assignment operations.
@@ -61,29 +72,54 @@
 
   ~Shrinker();
 
-  // Sets the message consumer to the given |consumer|. The |consumer| will be
-  // invoked once for each message communicated from the library.
-  void SetMessageConsumer(MessageConsumer consumer);
-
-  // Requires that when |transformation_sequence_in| is applied to |binary_in|
-  // with initial facts |initial_facts|, the resulting binary is interesting
-  // according to |interestingness_function|.
+  // Requires that when |transformation_sequence_in_| is applied to |binary_in_|
+  // with initial facts |initial_facts_|, the resulting binary is interesting
+  // according to |interestingness_function_|.
   //
-  // Produces, via |transformation_sequence_out|, a subsequence of
-  // |transformation_sequence_in| that, when applied with initial facts
-  // |initial_facts|, produces a binary (captured via |binary_out|) that is
-  // also interesting according to |interestingness_function|.
-  ShrinkerResultStatus Run(
-      const std::vector<uint32_t>& binary_in,
-      const protobufs::FactSequence& initial_facts,
-      const protobufs::TransformationSequence& transformation_sequence_in,
-      const InterestingnessFunction& interestingness_function,
-      std::vector<uint32_t>* binary_out,
-      protobufs::TransformationSequence* transformation_sequence_out) const;
+  // If shrinking succeeded -- possibly terminating early due to reaching the
+  // shrinker's step limit -- an associated result status is returned together
+  // with a subsequence of |transformation_sequence_in_| that, when applied
+  // to |binary_in_| with initial facts |initial_facts_|, produces a binary
+  // that is also interesting according to |interestingness_function_|; this
+  // binary is also returned.
+  //
+  // If shrinking failed for some reason, an appropriate result status is
+  // returned together with an empty binary and empty transformation sequence.
+  ShrinkerResult Run();
 
  private:
-  struct Impl;                  // Opaque struct for holding internal data.
-  std::unique_ptr<Impl> impl_;  // Unique pointer to internal data.
+  // Returns the id bound for the given SPIR-V binary, which is assumed to be
+  // valid.
+  uint32_t GetIdBound(const std::vector<uint32_t>& binary) 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 binary to which transformations are to 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_;
+
+  // The series of transformations to be shrunk.
+  const protobufs::TransformationSequence& transformation_sequence_in_;
+
+  // Function that decides whether a given module is interesting.
+  const InterestingnessFunction& interestingness_function_;
+
+  // Step limit to decide when to terminate shrinking early.
+  const uint32_t step_limit_;
+
+  // Determines whether to check for validity during the replaying of
+  // transformations.
+  const bool validate_during_replay_;
+
+  // Options to control validation.
+  spv_validator_options validator_options_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index a31384f..ebbc393 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -18,6 +18,7 @@
 
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/transformation_access_chain.h"
+#include "source/fuzz/transformation_add_bit_instruction_synonym.h"
 #include "source/fuzz/transformation_add_constant_boolean.h"
 #include "source/fuzz/transformation_add_constant_composite.h"
 #include "source/fuzz/transformation_add_constant_null.h"
@@ -26,12 +27,16 @@
 #include "source/fuzz/transformation_add_dead_block.h"
 #include "source/fuzz/transformation_add_dead_break.h"
 #include "source/fuzz/transformation_add_dead_continue.h"
+#include "source/fuzz/transformation_add_early_terminator_wrapper.h"
 #include "source/fuzz/transformation_add_function.h"
 #include "source/fuzz/transformation_add_global_undef.h"
 #include "source/fuzz/transformation_add_global_variable.h"
 #include "source/fuzz/transformation_add_image_sample_unused_components.h"
 #include "source/fuzz/transformation_add_local_variable.h"
+#include "source/fuzz/transformation_add_loop_preheader.h"
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
+#include "source/fuzz/transformation_add_opphi_synonym.h"
 #include "source/fuzz/transformation_add_parameter.h"
 #include "source/fuzz/transformation_add_relaxed_decoration.h"
 #include "source/fuzz/transformation_add_spec_constant_op.h"
@@ -48,25 +53,41 @@
 #include "source/fuzz/transformation_adjust_branch_weights.h"
 #include "source/fuzz/transformation_composite_construct.h"
 #include "source/fuzz/transformation_composite_extract.h"
+#include "source/fuzz/transformation_composite_insert.h"
 #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
+#include "source/fuzz/transformation_duplicate_region_with_selection.h"
 #include "source/fuzz/transformation_equation_instruction.h"
+#include "source/fuzz/transformation_expand_vector_reduction.h"
+#include "source/fuzz/transformation_flatten_conditional_branch.h"
 #include "source/fuzz/transformation_function_call.h"
+#include "source/fuzz/transformation_inline_function.h"
 #include "source/fuzz/transformation_invert_comparison_operator.h"
 #include "source/fuzz/transformation_load.h"
+#include "source/fuzz/transformation_make_vector_operation_dynamic.h"
 #include "source/fuzz/transformation_merge_blocks.h"
+#include "source/fuzz/transformation_merge_function_returns.h"
 #include "source/fuzz/transformation_move_block_down.h"
+#include "source/fuzz/transformation_move_instruction_down.h"
+#include "source/fuzz/transformation_mutate_pointer.h"
 #include "source/fuzz/transformation_outline_function.h"
 #include "source/fuzz/transformation_permute_function_parameters.h"
 #include "source/fuzz/transformation_permute_phi_operands.h"
+#include "source/fuzz/transformation_propagate_instruction_down.h"
+#include "source/fuzz/transformation_propagate_instruction_up.h"
 #include "source/fuzz/transformation_push_id_through_variable.h"
 #include "source/fuzz/transformation_record_synonymous_constants.h"
+#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
+#include "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
 #include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
 #include "source/fuzz/transformation_replace_copy_object_with_store_load.h"
 #include "source/fuzz/transformation_replace_id_with_synonym.h"
+#include "source/fuzz/transformation_replace_irrelevant_id.h"
 #include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
 #include "source/fuzz/transformation_replace_load_store_with_copy_memory.h"
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
 #include "source/fuzz/transformation_replace_parameter_with_global.h"
 #include "source/fuzz/transformation_replace_params_with_struct.h"
 #include "source/fuzz/transformation_set_function_control.h"
@@ -79,6 +100,8 @@
 #include "source/fuzz/transformation_swap_conditional_branch_operands.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/util/make_unique.h"
 
 namespace spvtools {
@@ -91,6 +114,10 @@
   switch (message.transformation_case()) {
     case protobufs::Transformation::TransformationCase::kAccessChain:
       return MakeUnique<TransformationAccessChain>(message.access_chain());
+    case protobufs::Transformation::TransformationCase::
+        kAddBitInstructionSynonym:
+      return MakeUnique<TransformationAddBitInstructionSynonym>(
+          message.add_bit_instruction_synonym());
     case protobufs::Transformation::TransformationCase::kAddConstantBoolean:
       return MakeUnique<TransformationAddConstantBoolean>(
           message.add_constant_boolean());
@@ -112,6 +139,10 @@
     case protobufs::Transformation::TransformationCase::kAddDeadContinue:
       return MakeUnique<TransformationAddDeadContinue>(
           message.add_dead_continue());
+    case protobufs::Transformation::TransformationCase::
+        kAddEarlyTerminatorWrapper:
+      return MakeUnique<TransformationAddEarlyTerminatorWrapper>(
+          message.add_early_terminator_wrapper());
     case protobufs::Transformation::TransformationCase::kAddFunction:
       return MakeUnique<TransformationAddFunction>(message.add_function());
     case protobufs::Transformation::TransformationCase::kAddGlobalUndef:
@@ -127,10 +158,20 @@
     case protobufs::Transformation::TransformationCase::kAddLocalVariable:
       return MakeUnique<TransformationAddLocalVariable>(
           message.add_local_variable());
+    case protobufs::Transformation::TransformationCase::kAddLoopPreheader:
+      return MakeUnique<TransformationAddLoopPreheader>(
+          message.add_loop_preheader());
+    case protobufs::Transformation::TransformationCase::
+        kAddLoopToCreateIntConstantSynonym:
+      return MakeUnique<TransformationAddLoopToCreateIntConstantSynonym>(
+          message.add_loop_to_create_int_constant_synonym());
     case protobufs::Transformation::TransformationCase::
         kAddNoContractionDecoration:
       return MakeUnique<TransformationAddNoContractionDecoration>(
           message.add_no_contraction_decoration());
+    case protobufs::Transformation::TransformationCase::kAddOpphiSynonym:
+      return MakeUnique<TransformationAddOpPhiSynonym>(
+          message.add_opphi_synonym());
     case protobufs::Transformation::TransformationCase::kAddParameter:
       return MakeUnique<TransformationAddParameter>(message.add_parameter());
     case protobufs::Transformation::TransformationCase::kAddRelaxedDecoration:
@@ -171,25 +212,54 @@
     case protobufs::Transformation::TransformationCase::kCompositeExtract:
       return MakeUnique<TransformationCompositeExtract>(
           message.composite_extract());
+    case protobufs::Transformation::TransformationCase::kCompositeInsert:
+      return MakeUnique<TransformationCompositeInsert>(
+          message.composite_insert());
     case protobufs::Transformation::TransformationCase::
         kComputeDataSynonymFactClosure:
       return MakeUnique<TransformationComputeDataSynonymFactClosure>(
           message.compute_data_synonym_fact_closure());
+    case protobufs::Transformation::TransformationCase::
+        kDuplicateRegionWithSelection:
+      return MakeUnique<TransformationDuplicateRegionWithSelection>(
+          message.duplicate_region_with_selection());
     case protobufs::Transformation::TransformationCase::kEquationInstruction:
       return MakeUnique<TransformationEquationInstruction>(
           message.equation_instruction());
+    case protobufs::Transformation::TransformationCase::kExpandVectorReduction:
+      return MakeUnique<TransformationExpandVectorReduction>(
+          message.expand_vector_reduction());
+    case protobufs::Transformation::TransformationCase::
+        kFlattenConditionalBranch:
+      return MakeUnique<TransformationFlattenConditionalBranch>(
+          message.flatten_conditional_branch());
     case protobufs::Transformation::TransformationCase::kFunctionCall:
       return MakeUnique<TransformationFunctionCall>(message.function_call());
+    case protobufs::Transformation::TransformationCase::kInlineFunction:
+      return MakeUnique<TransformationInlineFunction>(
+          message.inline_function());
     case protobufs::Transformation::TransformationCase::
         kInvertComparisonOperator:
       return MakeUnique<TransformationInvertComparisonOperator>(
           message.invert_comparison_operator());
     case protobufs::Transformation::TransformationCase::kLoad:
       return MakeUnique<TransformationLoad>(message.load());
+    case protobufs::Transformation::TransformationCase::
+        kMakeVectorOperationDynamic:
+      return MakeUnique<TransformationMakeVectorOperationDynamic>(
+          message.make_vector_operation_dynamic());
     case protobufs::Transformation::TransformationCase::kMergeBlocks:
       return MakeUnique<TransformationMergeBlocks>(message.merge_blocks());
+    case protobufs::Transformation::TransformationCase::kMergeFunctionReturns:
+      return MakeUnique<TransformationMergeFunctionReturns>(
+          message.merge_function_returns());
     case protobufs::Transformation::TransformationCase::kMoveBlockDown:
       return MakeUnique<TransformationMoveBlockDown>(message.move_block_down());
+    case protobufs::Transformation::TransformationCase::kMoveInstructionDown:
+      return MakeUnique<TransformationMoveInstructionDown>(
+          message.move_instruction_down());
+    case protobufs::Transformation::TransformationCase::kMutatePointer:
+      return MakeUnique<TransformationMutatePointer>(message.mutate_pointer());
     case protobufs::Transformation::TransformationCase::kOutlineFunction:
       return MakeUnique<TransformationOutlineFunction>(
           message.outline_function());
@@ -200,6 +270,13 @@
     case protobufs::Transformation::TransformationCase::kPermutePhiOperands:
       return MakeUnique<TransformationPermutePhiOperands>(
           message.permute_phi_operands());
+    case protobufs::Transformation::TransformationCase::
+        kPropagateInstructionDown:
+      return MakeUnique<TransformationPropagateInstructionDown>(
+          message.propagate_instruction_down());
+    case protobufs::Transformation::TransformationCase::kPropagateInstructionUp:
+      return MakeUnique<TransformationPropagateInstructionUp>(
+          message.propagate_instruction_up());
     case protobufs::Transformation::TransformationCase::kPushIdThroughVariable:
       return MakeUnique<TransformationPushIdThroughVariable>(
           message.push_id_through_variable());
@@ -208,14 +285,18 @@
       return MakeUnique<TransformationRecordSynonymousConstants>(
           message.record_synonymous_constants());
     case protobufs::Transformation::TransformationCase::
-        kReplaceParameterWithGlobal:
-      return MakeUnique<TransformationReplaceParameterWithGlobal>(
-          message.replace_parameter_with_global());
+        kReplaceAddSubMulWithCarryingExtended:
+      return MakeUnique<TransformationReplaceAddSubMulWithCarryingExtended>(
+          message.replace_add_sub_mul_with_carrying_extended());
     case protobufs::Transformation::TransformationCase::
         kReplaceBooleanConstantWithConstantBinary:
       return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
           message.replace_boolean_constant_with_constant_binary());
     case protobufs::Transformation::TransformationCase::
+        kReplaceBranchFromDeadBlockWithExit:
+      return MakeUnique<TransformationReplaceBranchFromDeadBlockWithExit>(
+          message.replace_branch_from_dead_block_with_exit());
+    case protobufs::Transformation::TransformationCase::
         kReplaceConstantWithUniform:
       return MakeUnique<TransformationReplaceConstantWithUniform>(
           message.replace_constant_with_uniform());
@@ -230,6 +311,9 @@
     case protobufs::Transformation::TransformationCase::kReplaceIdWithSynonym:
       return MakeUnique<TransformationReplaceIdWithSynonym>(
           message.replace_id_with_synonym());
+    case protobufs::Transformation::TransformationCase::kReplaceIrrelevantId:
+      return MakeUnique<TransformationReplaceIrrelevantId>(
+          message.replace_irrelevant_id());
     case protobufs::Transformation::TransformationCase::
         kReplaceLinearAlgebraInstruction:
       return MakeUnique<TransformationReplaceLinearAlgebraInstruction>(
@@ -239,9 +323,21 @@
       return MakeUnique<TransformationReplaceLoadStoreWithCopyMemory>(
           message.replace_load_store_with_copy_memory());
     case protobufs::Transformation::TransformationCase::
+        kReplaceOpselectWithConditionalBranch:
+      return MakeUnique<TransformationReplaceOpSelectWithConditionalBranch>(
+          message.replace_opselect_with_conditional_branch());
+    case protobufs::Transformation::TransformationCase::
+        kReplaceParameterWithGlobal:
+      return MakeUnique<TransformationReplaceParameterWithGlobal>(
+          message.replace_parameter_with_global());
+    case protobufs::Transformation::TransformationCase::
         kReplaceParamsWithStruct:
       return MakeUnique<TransformationReplaceParamsWithStruct>(
           message.replace_params_with_struct());
+    case protobufs::Transformation::TransformationCase::
+        kReplaceOpphiIdFromDeadPredecessor:
+      return MakeUnique<TransformationReplaceOpPhiIdFromDeadPredecessor>(
+          message.replace_opphi_id_from_dead_predecessor());
     case protobufs::Transformation::TransformationCase::kSetFunctionControl:
       return MakeUnique<TransformationSetFunctionControl>(
           message.set_function_control());
@@ -271,6 +367,13 @@
           message.toggle_access_chain_instruction());
     case protobufs::Transformation::TransformationCase::kVectorShuffle:
       return MakeUnique<TransformationVectorShuffle>(message.vector_shuffle());
+    case protobufs::Transformation::TransformationCase::
+        kWrapEarlyTerminatorInFunction:
+      return MakeUnique<TransformationWrapEarlyTerminatorInFunction>(
+          message.wrap_early_terminator_in_function());
+    case protobufs::Transformation::TransformationCase::kWrapRegionInSelection:
+      return MakeUnique<TransformationWrapRegionInSelection>(
+          message.wrap_region_in_selection());
     case protobufs::Transformation::TRANSFORMATION_NOT_SET:
       assert(false && "An unset transformation was encountered.");
       return nullptr;
diff --git a/source/fuzz/transformation.h b/source/fuzz/transformation.h
index dbd0fe2..8d618e8 100644
--- a/source/fuzz/transformation.h
+++ b/source/fuzz/transformation.h
@@ -16,6 +16,7 @@
 #define SOURCE_FUZZ_TRANSFORMATION_H_
 
 #include <memory>
+#include <unordered_set>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation_context.h"
@@ -58,6 +59,13 @@
 
 class Transformation {
  public:
+  virtual ~Transformation();
+
+  // Factory method to obtain a transformation object from the protobuf
+  // representation of a transformation given by |message|.
+  static std::unique_ptr<Transformation> FromMessage(
+      const protobufs::Transformation& message);
+
   // A precondition that determines whether the transformation can be cleanly
   // applied in a semantics-preserving manner to the SPIR-V module given by
   // |ir_context|, in the presence of facts and other contextual information
@@ -77,16 +85,13 @@
   virtual void Apply(opt::IRContext* ir_context,
                      TransformationContext* transformation_context) const = 0;
 
+  // Returns the set of fresh ids that appear in the transformation's protobuf
+  // message.
+  virtual std::unordered_set<uint32_t> GetFreshIds() const = 0;
+
   // Turns the transformation into a protobuf message for serialization.
   virtual protobufs::Transformation ToMessage() const = 0;
 
-  virtual ~Transformation();
-
-  // Factory method to obtain a transformation object from the protobuf
-  // representation of a transformation given by |message|.
-  static std::unique_ptr<Transformation> FromMessage(
-      const protobufs::Transformation& message);
-
   // Helper that returns true if and only if (a) |id| is a fresh id for the
   // module, and (b) |id| is not in |ids_used_by_this_transformation|, a set of
   // ids already known to be in use by a transformation.  This is useful when
diff --git a/source/fuzz/transformation_access_chain.cpp b/source/fuzz/transformation_access_chain.cpp
index 3366869..daf73d6 100644
--- a/source/fuzz/transformation_access_chain.cpp
+++ b/source/fuzz/transformation_access_chain.cpp
@@ -407,5 +407,14 @@
   return true;
 }
 
+std::unordered_set<uint32_t> TransformationAccessChain::GetFreshIds() const {
+  std::unordered_set<uint32_t> result = {message_.fresh_id()};
+  for (auto& fresh_ids_for_clamping : message_.fresh_ids_for_clamping()) {
+    result.insert(fresh_ids_for_clamping.first());
+    result.insert(fresh_ids_for_clamping.second());
+  }
+  return result;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_access_chain.h b/source/fuzz/transformation_access_chain.h
index db5b8e6..02cdc32 100644
--- a/source/fuzz/transformation_access_chain.h
+++ b/source/fuzz/transformation_access_chain.h
@@ -76,6 +76,8 @@
   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:
diff --git a/source/fuzz/transformation_add_bit_instruction_synonym.cpp b/source/fuzz/transformation_add_bit_instruction_synonym.cpp
new file mode 100644
index 0000000..9fdc37e
--- /dev/null
+++ b/source/fuzz/transformation_add_bit_instruction_synonym.cpp
@@ -0,0 +1,252 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_bit_instruction_synonym.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym(
+    const spvtools::fuzz::protobufs::TransformationAddBitInstructionSynonym&
+        message)
+    : message_(message) {}
+
+TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym(
+    const uint32_t instruction_result_id,
+    const std::vector<uint32_t>& fresh_ids) {
+  message_.set_instruction_result_id(instruction_result_id);
+  *message_.mutable_fresh_ids() =
+      google::protobuf::RepeatedField<google::protobuf::uint32>(
+          fresh_ids.begin(), fresh_ids.end());
+}
+
+bool TransformationAddBitInstructionSynonym::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  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)) {
+    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;
+  }
+
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3791):
+  //  This condition could be relaxed if the index exists as another integer
+  //  type.
+  // All bit indexes must be defined as 32-bit unsigned integers.
+  uint32_t width = ir_context->get_type_mgr()
+                       ->GetType(instruction->type_id())
+                       ->AsInteger()
+                       ->width();
+  for (uint32_t i = 0; i < width; i++) {
+    if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
+                                             {i}, 32, false, false)) {
+      return false;
+    }
+  }
+
+  // |message_.fresh_ids.size| must have the exact number of fresh ids required
+  // to apply the transformation.
+  if (static_cast<uint32_t>(message_.fresh_ids().size()) !=
+      GetRequiredFreshIdCount(ir_context, instruction)) {
+    return false;
+  }
+
+  // All ids in |message_.fresh_ids| must be fresh.
+  for (uint32_t fresh_id : message_.fresh_ids()) {
+    if (!fuzzerutil::IsFreshId(ir_context, fresh_id)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationAddBitInstructionSynonym::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto bit_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
+
+  // Use an appropriate helper function to add the new instruction and new
+  // synonym fact.  The helper function should take care of invalidating
+  // analyses before adding facts.
+  switch (bit_instruction->opcode()) {
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+      AddOpBitwiseOrOpNotSynonym(ir_context, transformation_context,
+                                 bit_instruction);
+      break;
+    default:
+      assert(false && "Should be unreachable.");
+      break;
+  }
+}
+
+protobufs::Transformation TransformationAddBitInstructionSynonym::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_add_bit_instruction_synonym() = message_;
+  return result;
+}
+
+uint32_t TransformationAddBitInstructionSynonym::GetRequiredFreshIdCount(
+    opt::IRContext* ir_context, opt::Instruction* bit_instruction) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557):
+  //  Right now, only certain operations are supported.
+  switch (bit_instruction->opcode()) {
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+      return (2 + bit_instruction->NumInOperands()) *
+                 ir_context->get_type_mgr()
+                     ->GetType(bit_instruction->type_id())
+                     ->AsInteger()
+                     ->width() -
+             1;
+    default:
+      assert(false && "Unsupported bit instruction.");
+      return 0;
+  }
+}
+
+void TransformationAddBitInstructionSynonym::AddOpBitwiseOrOpNotSynonym(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    opt::Instruction* bit_instruction) const {
+  // Fresh id iterator.
+  auto fresh_id = message_.fresh_ids().begin();
+
+  // |width| is the bit width of operands (8, 16, 32 or 64).
+  const uint32_t width = ir_context->get_type_mgr()
+                             ->GetType(bit_instruction->type_id())
+                             ->AsInteger()
+                             ->width();
+
+  // |count| is the number of bits to be extracted and inserted at a time.
+  const uint32_t count = fuzzerutil::MaybeGetIntegerConstant(
+      ir_context, *transformation_context, {1}, 32, false, false);
+
+  // |extracted_bit_instructions| is the collection of OpBiwise* or OpNot
+  // instructions that evaluate the extracted bits. Those ids will be used to
+  // insert the result bits.
+  std::vector<uint32_t> extracted_bit_instructions(width);
+
+  for (uint32_t i = 0; i < width; i++) {
+    // |offset| is the current bit index.
+    uint32_t offset = fuzzerutil::MaybeGetIntegerConstant(
+        ir_context, *transformation_context, {i}, 32, false, false);
+
+    // |bit_extract_ids| are the two extracted bits from the operands.
+    opt::Instruction::OperandList bit_extract_ids;
+
+    // Extracts the i-th bit from operands.
+    for (auto operand = bit_instruction->begin() + 2;
+         operand != bit_instruction->end(); operand++) {
+      auto bit_extract =
+          opt::Instruction(ir_context, SpvOpBitFieldUExtract,
+                           bit_instruction->type_id(), *fresh_id++,
+                           {{SPV_OPERAND_TYPE_ID, operand->words},
+                            {SPV_OPERAND_TYPE_ID, {offset}},
+                            {SPV_OPERAND_TYPE_ID, {count}}});
+      bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_extract));
+      fuzzerutil::UpdateModuleIdBound(ir_context, bit_extract.result_id());
+      bit_extract_ids.push_back(
+          {SPV_OPERAND_TYPE_ID, {bit_extract.result_id()}});
+    }
+
+    // Applies |bit_instruction| to the extracted bits.
+    auto extracted_bit_instruction = opt::Instruction(
+        ir_context, bit_instruction->opcode(), bit_instruction->type_id(),
+        *fresh_id++, bit_extract_ids);
+    bit_instruction->InsertBefore(
+        MakeUnique<opt::Instruction>(extracted_bit_instruction));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    extracted_bit_instruction.result_id());
+    extracted_bit_instructions[i] = extracted_bit_instruction.result_id();
+  }
+
+  // The first two ids in |extracted_bit_instructions| are used to insert the
+  // first two bits of the result.
+  uint32_t offset = fuzzerutil::MaybeGetIntegerConstant(
+      ir_context, *transformation_context, {1}, 32, false, false);
+  auto bit_insert = opt::Instruction(
+      ir_context, SpvOpBitFieldInsert, bit_instruction->type_id(), *fresh_id++,
+      {{SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[0]}},
+       {SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[1]}},
+       {SPV_OPERAND_TYPE_ID, {offset}},
+       {SPV_OPERAND_TYPE_ID, {count}}});
+  bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert));
+  fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id());
+
+  // Inserts the remaining bits.
+  for (uint32_t i = 2; i < width; i++) {
+    offset = fuzzerutil::MaybeGetIntegerConstant(
+        ir_context, *transformation_context, {i}, 32, false, false);
+    bit_insert = opt::Instruction(
+        ir_context, SpvOpBitFieldInsert, bit_instruction->type_id(),
+        *fresh_id++,
+        {{SPV_OPERAND_TYPE_ID, {bit_insert.result_id()}},
+         {SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[i]}},
+         {SPV_OPERAND_TYPE_ID, {offset}},
+         {SPV_OPERAND_TYPE_ID, {count}}});
+    bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert));
+    fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id());
+  }
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // We only add a synonym fact if the bit instruction is not irrelevant, and if
+  // the new result id we would make it synonymous with is not irrelevant.  (It
+  // could be irrelevant if we are in a dead block.)
+  if (!transformation_context->GetFactManager()->IdIsIrrelevant(
+          bit_instruction->result_id()) &&
+      !transformation_context->GetFactManager()->IdIsIrrelevant(
+          bit_insert.result_id())) {
+    // Adds the fact that the last |bit_insert| instruction is synonymous of
+    // |bit_instruction|.
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(bit_insert.result_id(), {}),
+        MakeDataDescriptor(bit_instruction->result_id(), {}));
+  }
+}
+
+std::unordered_set<uint32_t>
+TransformationAddBitInstructionSynonym::GetFreshIds() const {
+  std::unordered_set<uint32_t> result;
+  for (auto id : message_.fresh_ids()) {
+    result.insert(id);
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_bit_instruction_synonym.h b/source/fuzz/transformation_add_bit_instruction_synonym.h
new file mode 100644
index 0000000..ed1a0af
--- /dev/null
+++ b/source/fuzz/transformation_add_bit_instruction_synonym.h
@@ -0,0 +1,143 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_BIT_INSTRUCTION_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_BIT_INSTRUCTION_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 {
+
+// clang-format off
+// SPIR-V code to help understand the transformation.
+//
+// ----------------------------------------------------------------------------------------------------------------
+// |               Reference shader              |                         Variant shader                         |
+// ----------------------------------------------------------------------------------------------------------------
+// |      OpCapability Shader                    |       OpCapability Shader                                      |
+// |      OpCapability Int8                      |       OpCapability Int8                                        |
+// | %1 = OpExtInstImport "GLSL.std.450"         |  %1 = OpExtInstImport "GLSL.std.450"                           |
+// |      OpMemoryModel Logical GLSL450          |       OpMemoryModel Logical GLSL450                            |
+// |      OpEntryPoint Vertex %7 "main"          |       OpEntryPoint Vertex %7 "main"                            |
+// |                                             |                                                                |
+// | ; Types                                     | ; Types                                                        |
+// | %2 = OpTypeInt 8 0                          |  %2 = OpTypeInt 8 0                                            |
+// | %3 = OpTypeVoid                             |  %3 = OpTypeVoid                                               |
+// | %4 = OpTypeFunction %3                      |  %4 = OpTypeFunction %3                                        |
+// |                                             |                                                                |
+// | ; Constants                                 | ; Constants                                                    |
+// | %5 = OpConstant %2 0                        |  %5 = OpConstant %2 0                                          |
+// | %6 = OpConstant %2 1                        |  %6 = OpConstant %2 1                                          |
+// |                                             | %10 = OpConstant %2 2                                          |
+// | ; main function                             | %11 = OpConstant %2 3                                          |
+// | %7 = OpFunction %3 None %4                  | %12 = OpConstant %2 4                                          |
+// | %8 = OpLabel                                | %13 = OpConstant %2 5                                          |
+// | %9 = OpBitwiseOr %2 %5 %6 ; bit instruction | %14 = OpConstant %2 6                                          |
+// |       OpReturn                              | %15 = OpConstant %2 7                                          |
+// |       OpFunctionEnd                         |                                                                |
+// |                                             | ; main function                                                |
+// |                                             |  %7 = OpFunction %3 None %4                                    |
+// |                                             |  %8 = OpLabel                                                  |
+// |                                             |                                                                |
+// |                                             | %16 = OpBitFieldUExtract %2 %5 %5 %6 ; extracts bit 0 from %5  |
+// |                                             | %17 = OpBitFieldUExtract %2 %6 %5 %6 ; extracts bit 0 from %6  |
+// |                                             | %18 = OpBitwiseOr %2 %16 %17                                   |
+// |                                             |                                                                |
+// |                                             | %19 = OpBitFieldUExtract %2 %5 %6 %6 ; extracts bit 1 from %5  |
+// |                                             | %20 = OpBitFieldUExtract %2 %6 %6 %6 ; extracts bit 1 from %6  |
+// |                                             | %21 = OpBitwiseOr %2 %19 %20                                   |
+// |                                             |                                                                |
+// |                                             | %22 = OpBitFieldUExtract %2 %5 %10 %6 ; extracts bit 2 from %5 |
+// |                                             | %23 = OpBitFieldUExtract %2 %6 %10 %6 ; extracts bit 2 from %6 |
+// |                                             | %24 = OpBitwiseOr %2 %22 %23                                   |
+// |                                             |                                                                |
+// |                                             | %25 = OpBitFieldUExtract %2 %5 %11 %6 ; extracts bit 3 from %5 |
+// |                                             | %26 = OpBitFieldUExtract %2 %6 %11 %6 ; extracts bit 3 from %6 |
+// |                                             | %27 = OpBitwiseOr %2 %25 %26                                   |
+// |                                             |                                                                |
+// |                                             | %28 = OpBitFieldUExtract %2 %5 %12 %6 ; extracts bit 4 from %5 |
+// |                                             | %29 = OpBitFieldUExtract %2 %6 %12 %6 ; extracts bit 4 from %6 |
+// |                                             | %30 = OpBitwiseOr %2 %28 %29                                   |
+// |                                             |                                                                |
+// |                                             | %31 = OpBitFieldUExtract %2 %5 %13 %6 ; extracts bit 5 from %5 |
+// |                                             | %32 = OpBitFieldUExtract %2 %6 %13 %6 ; extracts bit 5 from %6 |
+// |                                             | %33 = OpBitwiseOr %2 %31 %32                                   |
+// |                                             |                                                                |
+// |                                             | %34 = OpBitFieldUExtract %2 %5 %14 %6 ; extracts bit 6 from %5 |
+// |                                             | %35 = OpBitFieldUExtract %2 %6 %14 %6 ; extracts bit 6 from %6 |
+// |                                             | %36 = OpBitwiseOr %2 %34 %35                                   |
+// |                                             |                                                                |
+// |                                             | %37 = OpBitFieldUExtract %2 %5 %15 %6 ; extracts bit 7 from %5 |
+// |                                             | %38 = OpBitFieldUExtract %2 %6 %15 %6 ; extracts bit 7 from %6 |
+// |                                             | %39 = OpBitwiseOr %2 %37 %38                                   |
+// |                                             |                                                                |
+// |                                             | %40 = OpBitFieldInsert %2 %18 %21 %6 %6 ; inserts bit 1        |
+// |                                             | %41 = OpBitFieldInsert %2 %40 %24 %10 %6 ; inserts bit 2       |
+// |                                             | %42 = OpBitFieldInsert %2 %41 %27 %11 %6 ; inserts bit 3       |
+// |                                             | %43 = OpBitFieldInsert %2 %42 %30 %12 %6 ; inserts bit 4       |
+// |                                             | %44 = OpBitFieldInsert %2 %43 %33 %13 %6 ; inserts bit 5       |
+// |                                             | %45 = OpBitFieldInsert %2 %44 %36 %14 %6 ; inserts bit 6       |
+// |                                             | %46 = OpBitFieldInsert %2 %45 %39 %15 %6 ; inserts bit 7       |
+// |                                             |  %9 = OpBitwiseOr %2 %5 %6 ; bit instruction                   |
+// |                                             |       OpReturn                                                 |
+// |                                             |       OpFunctionEnd                                            |
+// ----------------------------------------------------------------------------------------------------------------
+//
+// After the transformation, %9 and %46 will be synonymous.
+// clang-format on
+class TransformationAddBitInstructionSynonym : public Transformation {
+ public:
+  explicit TransformationAddBitInstructionSynonym(
+      const protobufs::TransformationAddBitInstructionSynonym& message);
+
+  TransformationAddBitInstructionSynonym(
+      const uint32_t instruction_result_id,
+      const std::vector<uint32_t>& fresh_ids);
+
+  // - |message_.instruction_result_id| must be a bit instruction.
+  // - |message_.fresh_ids| must be fresh ids needed to apply the
+  //   transformation.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds a bit instruction synonym.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns the number of fresh ids required to apply the transformation.
+  static uint32_t GetRequiredFreshIdCount(opt::IRContext* ir_context,
+                                          opt::Instruction* bit_instruction);
+
+ private:
+  protobufs::TransformationAddBitInstructionSynonym message_;
+
+  // Adds OpBitwise* or OpNot synonym.
+  void AddOpBitwiseOrOpNotSynonym(opt::IRContext* ir_context,
+                                  TransformationContext* transformation_context,
+                                  opt::Instruction* bitwise_instruction) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_BIT_INSTRUCTION_SYNONYM_H_
diff --git a/source/fuzz/transformation_add_constant_boolean.cpp b/source/fuzz/transformation_add_constant_boolean.cpp
index 904ad61..937fdbc 100644
--- a/source/fuzz/transformation_add_constant_boolean.cpp
+++ b/source/fuzz/transformation_add_constant_boolean.cpp
@@ -63,5 +63,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddConstantBoolean::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_constant_boolean.h b/source/fuzz/transformation_add_constant_boolean.h
index d2f9e9a..d1d04ef 100644
--- a/source/fuzz/transformation_add_constant_boolean.h
+++ b/source/fuzz/transformation_add_constant_boolean.h
@@ -44,6 +44,8 @@
   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:
diff --git a/source/fuzz/transformation_add_constant_composite.cpp b/source/fuzz/transformation_add_constant_composite.cpp
index 99d88b4..0260321 100644
--- a/source/fuzz/transformation_add_constant_composite.cpp
+++ b/source/fuzz/transformation_add_constant_composite.cpp
@@ -50,7 +50,8 @@
     return false;
   }
   // Gather up the operands for the composite constant, in the process checking
-  // whether the given type really defines a composite.
+  // whether the given type really defines a composite and - in the case of a
+  // struct - whether its decorations are OK.
   std::vector<uint32_t> constituent_type_ids;
   switch (composite_type_instruction->opcode()) {
     case SpvOpTypeArray:
@@ -72,6 +73,14 @@
       }
       break;
     case SpvOpTypeStruct:
+      // We do not create constants of structs decorated with Block nor
+      // BufferBlock.  The SPIR-V spec does not explicitly disallow this, but it
+      // seems like a strange thing to do, so we disallow it to avoid triggering
+      // low priorty edge case issues related to it.
+      if (fuzzerutil::HasBlockOrBufferBlockDecoration(
+              ir_context, composite_type_instruction->result_id())) {
+        return false;
+      }
       composite_type_instruction->ForEachInOperand(
           [&constituent_type_ids](const uint32_t* member_type_id) {
             constituent_type_ids.push_back(*member_type_id);
@@ -133,5 +142,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddConstantComposite::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_constant_composite.h b/source/fuzz/transformation_add_constant_composite.h
index 2dddbfb..9e9222d 100644
--- a/source/fuzz/transformation_add_constant_composite.h
+++ b/source/fuzz/transformation_add_constant_composite.h
@@ -38,6 +38,8 @@
   // - |message_.type_id| must be the id of a composite type
   // - |message_.constituent_id| must refer to ids that match the constituent
   //   types of this composite type
+  // - If |message_.type_id| is a struct type, it must not have the Block or
+  //   BufferBlock decoration
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
@@ -50,6 +52,8 @@
   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:
diff --git a/source/fuzz/transformation_add_constant_null.cpp b/source/fuzz/transformation_add_constant_null.cpp
index dedbc21..3c66ab1 100644
--- a/source/fuzz/transformation_add_constant_null.cpp
+++ b/source/fuzz/transformation_add_constant_null.cpp
@@ -62,5 +62,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddConstantNull::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_constant_null.h b/source/fuzz/transformation_add_constant_null.h
index 590fc0d..bd08b1d 100644
--- a/source/fuzz/transformation_add_constant_null.h
+++ b/source/fuzz/transformation_add_constant_null.h
@@ -42,6 +42,8 @@
   void Apply(opt::IRContext* context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
  private:
diff --git a/source/fuzz/transformation_add_constant_scalar.cpp b/source/fuzz/transformation_add_constant_scalar.cpp
index 98bfbf0..9a6642a 100644
--- a/source/fuzz/transformation_add_constant_scalar.cpp
+++ b/source/fuzz/transformation_add_constant_scalar.cpp
@@ -64,13 +64,12 @@
 void TransformationAddConstantScalar::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  opt::Instruction::OperandList operand_list;
-  for (auto word : message_.word()) {
-    operand_list.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {word}});
-  }
   ir_context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
       ir_context, SpvOpConstant, message_.type_id(), message_.fresh_id(),
-      operand_list));
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_LITERAL_INTEGER,
+            std::vector<uint32_t>(message_.word().begin(),
+                                  message_.word().end())}})));
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
@@ -91,5 +90,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddConstantScalar::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_constant_scalar.h b/source/fuzz/transformation_add_constant_scalar.h
index 06a77fc..3f23907 100644
--- a/source/fuzz/transformation_add_constant_scalar.h
+++ b/source/fuzz/transformation_add_constant_scalar.h
@@ -47,6 +47,8 @@
   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:
diff --git a/source/fuzz/transformation_add_copy_memory.cpp b/source/fuzz/transformation_add_copy_memory.cpp
index e9c401d..44bb9c5 100644
--- a/source/fuzz/transformation_add_copy_memory.cpp
+++ b/source/fuzz/transformation_add_copy_memory.cpp
@@ -132,6 +132,10 @@
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
 
+  // Make sure our changes are analyzed
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+
   // Even though the copy memory instruction will - at least temporarily - lead
   // to the destination and source pointers referring to identical values, this
   // fact is not guaranteed to hold throughout execution of the SPIR-V code
@@ -140,10 +144,6 @@
   // pointer can be used freely by other fuzzer passes.
   transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
       message_.fresh_id());
-
-  // Make sure our changes are analyzed
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddCopyMemory::ToMessage() const {
@@ -162,8 +162,21 @@
   const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
   assert(type && "Instruction must have a valid type");
 
-  return type->AsPointer() &&
-         CanUsePointeeWithCopyMemory(*type->AsPointer()->pointee_type());
+  if (!type->AsPointer()) {
+    return false;
+  }
+
+  // We do not support copying memory from a pointer to a block-/buffer
+  // block-decorated struct.
+  auto pointee_type_inst = ir_context->get_def_use_mgr()
+                               ->GetDef(inst->type_id())
+                               ->GetSingleWordInOperand(1);
+  if (fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context,
+                                                  pointee_type_inst)) {
+    return false;
+  }
+
+  return CanUsePointeeWithCopyMemory(*type->AsPointer()->pointee_type());
 }
 
 bool TransformationAddCopyMemory::CanUsePointeeWithCopyMemory(
@@ -189,5 +202,9 @@
   }
 }
 
+std::unordered_set<uint32_t> TransformationAddCopyMemory::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_copy_memory.h b/source/fuzz/transformation_add_copy_memory.h
index 138d992..cc42f1e 100644
--- a/source/fuzz/transformation_add_copy_memory.h
+++ b/source/fuzz/transformation_add_copy_memory.h
@@ -41,6 +41,8 @@
   // - |fresh_id| must be a fresh id to copy memory into.
   // - type of |source_id| must be OpTypePointer where pointee can be used with
   //   OpCopyMemory.
+  // - If the pointee type of |source_id| is a struct type, it must not have the
+  //   Block or BufferBlock decoration.
   // - |storage_class| must be either Private or Function.
   // - type ids of instructions with result ids |source_id| and |initialize_id|
   //   must be the same.
@@ -54,6 +56,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if we can copy memory from |instruction| using OpCopyMemory.
diff --git a/source/fuzz/transformation_add_dead_block.cpp b/source/fuzz/transformation_add_dead_block.cpp
index 5b06e97..3d77a80 100644
--- a/source/fuzz/transformation_add_dead_block.cpp
+++ b/source/fuzz/transformation_add_dead_block.cpp
@@ -78,6 +78,27 @@
     return false;
   }
 
+  // |existing_block| must be reachable.
+  opt::DominatorAnalysis* dominator_analysis =
+      ir_context->GetDominatorAnalysis(existing_block->GetParent());
+  if (!dominator_analysis->IsReachable(existing_block->id())) {
+    return false;
+  }
+
+  assert(existing_block->id() != successor_block_id &&
+         "|existing_block| must be different from |successor_block_id|");
+
+  // Even though we know |successor_block_id| is not a merge block, it might
+  // still have multiple predecessors because divergent control flow is allowed
+  // to converge early (before the merge block). In this case, when we create
+  // 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|.
+  if (!dominator_analysis->Dominates(existing_block->id(),
+                                     successor_block_id)) {
+    return false;
+  }
+
   return true;
 }
 
@@ -136,10 +157,6 @@
   enclosing_function->InsertBasicBlockAfter(std::move(new_block),
                                             existing_block);
 
-  // Record the fact that the new block is dead.
-  transformation_context->GetFactManager()->AddFactBlockIsDead(
-      message_.fresh_id());
-
   // Fix up OpPhi instructions in the successor block, so that the values they
   // yield when control has transferred from the new block are the same as if
   // control had transferred from |message_.existing_block|.  This is guaranteed
@@ -160,6 +177,10 @@
   // Do not rely on any existing analysis results since the control flow graph
   // of the module has changed.
   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Record the fact that the new block is dead.
+  transformation_context->GetFactManager()->AddFactBlockIsDead(
+      message_.fresh_id());
 }
 
 protobufs::Transformation TransformationAddDeadBlock::ToMessage() const {
@@ -168,5 +189,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddDeadBlock::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_dead_block.h b/source/fuzz/transformation_add_dead_block.h
index 7d07616..50af6b0 100644
--- a/source/fuzz/transformation_add_dead_block.h
+++ b/source/fuzz/transformation_add_dead_block.h
@@ -53,6 +53,8 @@
   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:
diff --git a/source/fuzz/transformation_add_dead_break.cpp b/source/fuzz/transformation_add_dead_break.cpp
index 284174a..bc938d4 100644
--- a/source/fuzz/transformation_add_dead_break.cpp
+++ b/source/fuzz/transformation_add_dead_break.cpp
@@ -182,7 +182,8 @@
   auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
   ApplyImpl(cloned_context.get(), transformation_context);
   return fuzzerutil::IsValid(cloned_context.get(),
-                             transformation_context.GetValidatorOptions());
+                             transformation_context.GetValidatorOptions(),
+                             fuzzerutil::kSilentMessageConsumer);
 }
 
 void TransformationAddDeadBreak::Apply(
@@ -211,5 +212,9 @@
       message_.phi_id());
 }
 
+std::unordered_set<uint32_t> TransformationAddDeadBreak::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_dead_break.h b/source/fuzz/transformation_add_dead_break.h
index f010b11..afb8dc7 100644
--- a/source/fuzz/transformation_add_dead_break.h
+++ b/source/fuzz/transformation_add_dead_break.h
@@ -61,6 +61,8 @@
   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:
diff --git a/source/fuzz/transformation_add_dead_continue.cpp b/source/fuzz/transformation_add_dead_continue.cpp
index b5b7ae3..18b3c39 100644
--- a/source/fuzz/transformation_add_dead_continue.cpp
+++ b/source/fuzz/transformation_add_dead_continue.cpp
@@ -122,7 +122,8 @@
   auto cloned_context = fuzzerutil::CloneIRContext(ir_context);
   ApplyImpl(cloned_context.get(), transformation_context);
   return fuzzerutil::IsValid(cloned_context.get(),
-                             transformation_context.GetValidatorOptions());
+                             transformation_context.GetValidatorOptions(),
+                             fuzzerutil::kSilentMessageConsumer);
 }
 
 void TransformationAddDeadContinue::Apply(
@@ -158,5 +159,10 @@
       message_.phi_id());
 }
 
+std::unordered_set<uint32_t> TransformationAddDeadContinue::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_dead_continue.h b/source/fuzz/transformation_add_dead_continue.h
index b977bb2..27527e7 100644
--- a/source/fuzz/transformation_add_dead_continue.h
+++ b/source/fuzz/transformation_add_dead_continue.h
@@ -63,6 +63,8 @@
   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:
diff --git a/source/fuzz/transformation_add_early_terminator_wrapper.cpp b/source/fuzz/transformation_add_early_terminator_wrapper.cpp
new file mode 100644
index 0000000..0aa1214
--- /dev/null
+++ b/source/fuzz/transformation_add_early_terminator_wrapper.cpp
@@ -0,0 +1,110 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_early_terminator_wrapper.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddEarlyTerminatorWrapper::
+    TransformationAddEarlyTerminatorWrapper(
+        const spvtools::fuzz::protobufs::
+            TransformationAddEarlyTerminatorWrapper& message)
+    : message_(message) {}
+
+TransformationAddEarlyTerminatorWrapper::
+    TransformationAddEarlyTerminatorWrapper(uint32_t function_fresh_id,
+                                            uint32_t label_fresh_id,
+                                            SpvOp opcode) {
+  message_.set_function_fresh_id(function_fresh_id);
+  message_.set_label_fresh_id(label_fresh_id);
+  message_.set_opcode(opcode);
+}
+
+bool TransformationAddEarlyTerminatorWrapper::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  assert((message_.opcode() == SpvOpKill ||
+          message_.opcode() == SpvOpUnreachable ||
+          message_.opcode() == SpvOpTerminateInvocation) &&
+         "Invalid opcode.");
+
+  if (!fuzzerutil::IsFreshId(ir_context, message_.function_fresh_id())) {
+    return false;
+  }
+  if (!fuzzerutil::IsFreshId(ir_context, message_.label_fresh_id())) {
+    return false;
+  }
+  if (message_.function_fresh_id() == message_.label_fresh_id()) {
+    return false;
+  }
+  uint32_t void_type_id = fuzzerutil::MaybeGetVoidType(ir_context);
+  if (!void_type_id) {
+    return false;
+  }
+  return fuzzerutil::FindFunctionType(ir_context, {void_type_id});
+}
+
+void TransformationAddEarlyTerminatorWrapper::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.function_fresh_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.label_fresh_id());
+
+  // Create a basic block of the form:
+  // %label_fresh_id = OpLabel
+  //                   OpKill|Unreachable|TerminateInvocation
+  auto basic_block = MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpLabel, 0, message_.label_fresh_id(),
+      opt::Instruction::OperandList()));
+  basic_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, static_cast<SpvOp>(message_.opcode()), 0, 0,
+      opt::Instruction::OperandList()));
+
+  // Create a zero-argument void function.
+  auto void_type_id = fuzzerutil::MaybeGetVoidType(ir_context);
+  auto function = MakeUnique<opt::Function>(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpFunction, void_type_id, message_.function_fresh_id(),
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {SpvFunctionControlMaskNone}},
+           {SPV_OPERAND_TYPE_TYPE_ID,
+            {fuzzerutil::FindFunctionType(ir_context, {void_type_id})}}})));
+
+  // Add the basic block to the function as the sole block, and add the function
+  // to the module.
+  basic_block->SetParent(function.get());
+  function->AddBasicBlock(std::move(basic_block));
+  function->SetFunctionEnd(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpFunctionEnd, 0, 0, opt::Instruction::OperandList()));
+  ir_context->module()->AddFunction(std::move(function));
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+std::unordered_set<uint32_t>
+TransformationAddEarlyTerminatorWrapper::GetFreshIds() const {
+  return std::unordered_set<uint32_t>(
+      {message_.function_fresh_id(), message_.label_fresh_id()});
+}
+
+protobufs::Transformation TransformationAddEarlyTerminatorWrapper::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_add_early_terminator_wrapper() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_early_terminator_wrapper.h b/source/fuzz/transformation_add_early_terminator_wrapper.h
new file mode 100644
index 0000000..273037e
--- /dev/null
+++ b/source/fuzz/transformation_add_early_terminator_wrapper.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_add_early_terminator_wrapper_H_
+#define SOURCE_FUZZ_TRANSFORMATION_add_early_terminator_wrapper_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 TransformationAddEarlyTerminatorWrapper : public Transformation {
+ public:
+  explicit TransformationAddEarlyTerminatorWrapper(
+      const protobufs::TransformationAddEarlyTerminatorWrapper& message);
+
+  TransformationAddEarlyTerminatorWrapper(uint32_t function_fresh_id,
+                                          uint32_t label_fresh_id,
+                                          SpvOp opcode);
+
+  // - |message_.function_fresh_id| and |message_.label_fresh_id| must be fresh
+  //   and distinct.
+  // - OpTypeVoid must be declared in the module.
+  // - The module must contain a type for a zero-argument void function.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds a function to the module of the form:
+  //
+  // |message_.function_fresh_id| = OpFunction %void None %zero_args_return_void
+  //    |message_.label_fresh_id| = OpLabel
+  //                                |message_.opcode|
+  //                                OpFunctionEnd
+  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::TransformationAddEarlyTerminatorWrapper message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_add_early_terminator_wrapper_H_
diff --git a/source/fuzz/transformation_add_function.cpp b/source/fuzz/transformation_add_function.cpp
index 380d59c..214f741 100644
--- a/source/fuzz/transformation_add_function.cpp
+++ b/source/fuzz/transformation_add_function.cpp
@@ -135,7 +135,8 @@
   // Check whether the cloned module is still valid after adding the function.
   // If it is not, the transformation is not applicable.
   if (!fuzzerutil::IsValid(cloned_module.get(),
-                           transformation_context.GetValidatorOptions())) {
+                           transformation_context.GetValidatorOptions(),
+                           fuzzerutil::kSilentMessageConsumer)) {
     return false;
   }
 
@@ -151,7 +152,8 @@
     // It is simpler to rely on the validator to guard against this than to
     // consider all scenarios when making a function livesafe.
     if (!fuzzerutil::IsValid(cloned_module.get(),
-                             transformation_context.GetValidatorOptions())) {
+                             transformation_context.GetValidatorOptions(),
+                             fuzzerutil::kSilentMessageConsumer)) {
       return false;
     }
   }
@@ -168,6 +170,32 @@
   (void)(success);  // Keep release builds happy (otherwise they may complain
                     // that |success| is not used).
 
+  if (message_.is_livesafe()) {
+    // Make the function livesafe, which also should succeed.
+    success = TryToMakeFunctionLivesafe(ir_context, *transformation_context);
+    assert(success && "It should be possible to make the function livesafe.");
+    (void)(success);  // Keep release builds happy.
+  }
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  assert(message_.instruction(0).opcode() == SpvOpFunction &&
+         "The first instruction of an 'add function' transformation must be "
+         "OpFunction.");
+
+  if (message_.is_livesafe()) {
+    // Inform the fact manager that the function is livesafe.
+    transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
+        message_.instruction(0).result_id());
+  } else {
+    // Inform the fact manager that all blocks in the function are dead.
+    for (auto& inst : message_.instruction()) {
+      if (inst.opcode() == SpvOpLabel) {
+        transformation_context->GetFactManager()->AddFactBlockIsDead(
+            inst.result_id());
+      }
+    }
+  }
+
   // Record the fact that all pointer parameters and variables declared in the
   // function should be regarded as having irrelevant values.  This allows other
   // passes to store arbitrarily to such variables, and to pass them freely as
@@ -191,29 +219,6 @@
         break;
     }
   }
-
-  if (message_.is_livesafe()) {
-    // Make the function livesafe, which also should succeed.
-    success = TryToMakeFunctionLivesafe(ir_context, *transformation_context);
-    assert(success && "It should be possible to make the function livesafe.");
-    (void)(success);  // Keep release builds happy.
-
-    // Inform the fact manager that the function is livesafe.
-    assert(message_.instruction(0).opcode() == SpvOpFunction &&
-           "The first instruction of an 'add function' transformation must be "
-           "OpFunction.");
-    transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
-        message_.instruction(0).result_id());
-  } else {
-    // Inform the fact manager that all blocks in the function are dead.
-    for (auto& inst : message_.instruction()) {
-      if (inst.opcode() == SpvOpLabel) {
-        transformation_context->GetFactManager()->AddFactBlockIsDead(
-            inst.result_id());
-      }
-    }
-  }
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddFunction::ToMessage() const {
@@ -363,6 +368,22 @@
   return true;
 }
 
+uint32_t TransformationAddFunction::GetBackEdgeBlockId(
+    opt::IRContext* ir_context, uint32_t loop_header_block_id) {
+  const auto* loop_header_block =
+      ir_context->cfg()->block(loop_header_block_id);
+  assert(loop_header_block && "|loop_header_block_id| is invalid");
+
+  for (auto pred : ir_context->cfg()->preds(loop_header_block_id)) {
+    if (ir_context->GetDominatorAnalysis(loop_header_block->GetParent())
+            ->Dominates(loop_header_block_id, pred)) {
+      return pred;
+    }
+  }
+
+  return 0;
+}
+
 bool TransformationAddFunction::TryToAddLoopLimiters(
     opt::IRContext* ir_context, opt::Function* added_function) const {
   // Collect up all the loop headers so that we can subsequently add loop
@@ -474,21 +495,28 @@
   for (auto loop_header : loop_headers) {
     // Look for the loop's back-edge block.  This is a predecessor of the loop
     // header that is dominated by the loop header.
-    uint32_t back_edge_block_id = 0;
-    for (auto pred : ir_context->cfg()->preds(loop_header->id())) {
-      if (ir_context->GetDominatorAnalysis(added_function)
-              ->Dominates(loop_header->id(), pred)) {
-        back_edge_block_id = pred;
-        break;
-      }
-    }
+    const auto back_edge_block_id =
+        GetBackEdgeBlockId(ir_context, loop_header->id());
     if (!back_edge_block_id) {
       // The loop's back-edge block must be unreachable.  This means that the
       // loop cannot iterate, so there is no need to make it lifesafe; we can
       // move on from this loop.
       continue;
     }
-    auto back_edge_block = ir_context->cfg()->block(back_edge_block_id);
+
+    // If the loop's merge block is unreachable, then there are no constraints
+    // on where the merge block appears in relation to the blocks of the loop.
+    // This means we need to be careful when adding a branch from the back-edge
+    // block to the merge block: the branch might make the loop merge reachable,
+    // and it might then be dominated by the loop header and possibly by other
+    // blocks in the loop. Since a block needs to appear before those blocks it
+    // strictly dominates, this could make the module invalid. To avoid this
+    // problem we bail out in the case where the loop header does not dominate
+    // the loop merge.
+    if (!ir_context->GetDominatorAnalysis(added_function)
+             ->Dominates(loop_header->id(), loop_header->MergeBlockId())) {
+      return false;
+    }
 
     // Go through the sequence of loop limiter infos and find the one
     // corresponding to this loop.
@@ -560,6 +588,7 @@
     // %t4 = OpLogicalOr %bool %c %t3
     //       OpBranchConditional %t4 %loop_merge %loop_header
 
+    auto back_edge_block = ir_context->cfg()->block(back_edge_block_id);
     auto back_edge_block_terminator = back_edge_block->terminator();
     bool compare_using_greater_than_equal;
     if (back_edge_block_terminator->opcode() == SpvOpBranch) {
@@ -675,16 +704,10 @@
       }
 
       // Add the new edge, by changing OpBranch to OpBranchConditional.
-      // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3162): This
-      //  could be a problem if the merge block was originally unreachable: it
-      //  might now be dominated by other blocks that it appears earlier than in
-      //  the module.
       back_edge_block_terminator->SetOpcode(SpvOpBranchConditional);
       back_edge_block_terminator->SetInOperands(opt::Instruction::OperandList(
           {{SPV_OPERAND_TYPE_ID, {loop_limiter_info.compare_id()}},
-           {SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()}
-
-           },
+           {SPV_OPERAND_TYPE_ID, {loop_header->MergeBlockId()}},
            {SPV_OPERAND_TYPE_ID, {loop_header->id()}}}));
     }
 
@@ -909,5 +932,29 @@
   return ir_context->get_def_use_mgr()->GetDef(sub_object_type_id);
 }
 
+std::unordered_set<uint32_t> TransformationAddFunction::GetFreshIds() const {
+  std::unordered_set<uint32_t> result;
+  for (auto& instruction : message_.instruction()) {
+    result.insert(instruction.result_id());
+  }
+  if (message_.is_livesafe()) {
+    result.insert(message_.loop_limiter_variable_id());
+    for (auto& loop_limiter_info : message_.loop_limiter_info()) {
+      result.insert(loop_limiter_info.load_id());
+      result.insert(loop_limiter_info.increment_id());
+      result.insert(loop_limiter_info.compare_id());
+      result.insert(loop_limiter_info.logical_op_id());
+    }
+    for (auto& access_chain_clamping_info :
+         message_.access_chain_clamping_info()) {
+      for (auto& pair : access_chain_clamping_info.compare_and_select_ids()) {
+        result.insert(pair.first());
+        result.insert(pair.second());
+      }
+    }
+  }
+  return result;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_function.h b/source/fuzz/transformation_add_function.h
index 4a84c70..e5381d1 100644
--- a/source/fuzz/transformation_add_function.h
+++ b/source/fuzz/transformation_add_function.h
@@ -56,6 +56,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Helper method that, given composite type |composite_type_inst|, returns the
@@ -65,7 +67,12 @@
       opt::IRContext* ir_context, const opt::Instruction& composite_type_inst,
       uint32_t index_id);
 
- private:
+  // Returns id of the back-edge block, given the corresponding
+  // |loop_header_block_id|. |loop_header_block_id| must be the id of a loop
+  // header block. Returns 0 if the loop has no back-edge block.
+  static uint32_t GetBackEdgeBlockId(opt::IRContext* ir_context,
+                                     uint32_t loop_header_block_id);
+
   // Attempts to create a function from the series of instructions in
   // |message_.instruction| and add it to |ir_context|.
   //
@@ -86,6 +93,7 @@
   //   to add the function.
   bool TryToAddFunction(opt::IRContext* ir_context) const;
 
+ private:
   // Should only be called if |message_.is_livesafe| holds.  Attempts to make
   // the function livesafe (see FactFunctionIsLivesafe for a definition).
   // Returns false if this is not possible, due to |message_| or |ir_context|
diff --git a/source/fuzz/transformation_add_global_undef.cpp b/source/fuzz/transformation_add_global_undef.cpp
index ba45f22..7a90b82 100644
--- a/source/fuzz/transformation_add_global_undef.cpp
+++ b/source/fuzz/transformation_add_global_undef.cpp
@@ -58,5 +58,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddGlobalUndef::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_global_undef.h b/source/fuzz/transformation_add_global_undef.h
index c89fe9d..717dc9a 100644
--- a/source/fuzz/transformation_add_global_undef.h
+++ b/source/fuzz/transformation_add_global_undef.h
@@ -41,6 +41,8 @@
   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:
diff --git a/source/fuzz/transformation_add_global_variable.cpp b/source/fuzz/transformation_add_global_variable.cpp
index 303c4d9..dd04e48 100644
--- a/source/fuzz/transformation_add_global_variable.cpp
+++ b/source/fuzz/transformation_add_global_variable.cpp
@@ -98,15 +98,15 @@
       static_cast<SpvStorageClass>(message_.storage_class()),
       message_.initializer_id());
 
-  if (message_.value_is_irrelevant()) {
-    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
-        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);
+
+  if (message_.value_is_irrelevant()) {
+    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+        message_.fresh_id());
+  }
 }
 
 protobufs::Transformation TransformationAddGlobalVariable::ToMessage() const {
@@ -115,5 +115,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddGlobalVariable::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_global_variable.h b/source/fuzz/transformation_add_global_variable.h
index 89bd044..8d46edb 100644
--- a/source/fuzz/transformation_add_global_variable.h
+++ b/source/fuzz/transformation_add_global_variable.h
@@ -55,6 +55,8 @@
   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:
diff --git a/source/fuzz/transformation_add_image_sample_unused_components.cpp b/source/fuzz/transformation_add_image_sample_unused_components.cpp
index 1be1d43..ab48f0b 100644
--- a/source/fuzz/transformation_add_image_sample_unused_components.cpp
+++ b/source/fuzz/transformation_add_image_sample_unused_components.cpp
@@ -113,5 +113,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationAddImageSampleUnusedComponents::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_image_sample_unused_components.h b/source/fuzz/transformation_add_image_sample_unused_components.h
index 7493481..7486c76 100644
--- a/source/fuzz/transformation_add_image_sample_unused_components.h
+++ b/source/fuzz/transformation_add_image_sample_unused_components.h
@@ -45,6 +45,8 @@
   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:
diff --git a/source/fuzz/transformation_add_local_variable.cpp b/source/fuzz/transformation_add_local_variable.cpp
index a6b31b4..0a7a3da 100644
--- a/source/fuzz/transformation_add_local_variable.cpp
+++ b/source/fuzz/transformation_add_local_variable.cpp
@@ -74,11 +74,12 @@
                                message_.type_id(), message_.function_id(),
                                message_.initializer_id());
 
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
   if (message_.value_is_irrelevant()) {
     transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
         message_.fresh_id());
   }
-  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationAddLocalVariable::ToMessage() const {
@@ -87,5 +88,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddLocalVariable::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_local_variable.h b/source/fuzz/transformation_add_local_variable.h
index 6460904..963079f 100644
--- a/source/fuzz/transformation_add_local_variable.h
+++ b/source/fuzz/transformation_add_local_variable.h
@@ -50,6 +50,8 @@
   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:
diff --git a/source/fuzz/transformation_add_loop_preheader.cpp b/source/fuzz/transformation_add_loop_preheader.cpp
new file mode 100644
index 0000000..3d50fa9
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_preheader.cpp
@@ -0,0 +1,233 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 "transformation_add_loop_preheader.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/instruction.h"
+
+namespace spvtools {
+namespace fuzz {
+TransformationAddLoopPreheader::TransformationAddLoopPreheader(
+    const protobufs::TransformationAddLoopPreheader& message)
+    : message_(message) {}
+
+TransformationAddLoopPreheader::TransformationAddLoopPreheader(
+    uint32_t loop_header_block, uint32_t fresh_id,
+    std::vector<uint32_t> phi_id) {
+  message_.set_loop_header_block(loop_header_block);
+  message_.set_fresh_id(fresh_id);
+  for (auto id : phi_id) {
+    message_.add_phi_id(id);
+  }
+}
+
+bool TransformationAddLoopPreheader::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& /* unused */) const {
+  // |message_.loop_header_block()| must be the id of a loop header block.
+  opt::BasicBlock* loop_header_block =
+      fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block());
+  if (!loop_header_block || !loop_header_block->IsLoopHeader()) {
+    return false;
+  }
+
+  // The id for the preheader must actually be fresh.
+  std::set<uint32_t> used_ids;
+  if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.fresh_id(),
+                                                    ir_context, &used_ids)) {
+    return false;
+  }
+
+  size_t num_predecessors =
+      ir_context->cfg()->preds(message_.loop_header_block()).size();
+
+  // The block must have at least 2 predecessors (the back-edge block and
+  // another predecessor outside of the loop)
+  if (num_predecessors < 2) {
+    return false;
+  }
+
+  // If the block only has one predecessor outside of the loop (and thus 2 in
+  // total), then no additional fresh ids are necessary.
+  if (num_predecessors == 2) {
+    return true;
+  }
+
+  // Count the number of OpPhi instructions.
+  int32_t num_phi_insts = 0;
+  loop_header_block->ForEachPhiInst(
+      [&num_phi_insts](opt::Instruction* /* unused */) { num_phi_insts++; });
+
+  // There must be enough fresh ids for the OpPhi instructions.
+  if (num_phi_insts > message_.phi_id_size()) {
+    return false;
+  }
+
+  // Check that the needed ids are fresh and distinct.
+  for (int32_t i = 0; i < num_phi_insts; i++) {
+    if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.phi_id(i),
+                                                      ir_context, &used_ids)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationAddLoopPreheader::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* /* transformation_context */) const {
+  // Find the loop header.
+  opt::BasicBlock* loop_header =
+      fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block());
+
+  auto dominator_analysis =
+      ir_context->GetDominatorAnalysis(loop_header->GetParent());
+
+  uint32_t back_edge_block_id = 0;
+
+  // Update the branching instructions of the out-of-loop predecessors of the
+  // header. Set |back_edge_block_id| to be the id of the back-edge block.
+  ir_context->get_def_use_mgr()->ForEachUse(
+      loop_header->id(),
+      [this, &ir_context, &dominator_analysis, &loop_header,
+       &back_edge_block_id](opt::Instruction* use_inst, uint32_t use_index) {
+        if (dominator_analysis->Dominates(loop_header->GetLabelInst(),
+                                          use_inst)) {
+          // If |use_inst| is a branch instruction dominated by the header, the
+          // block containing it is the back-edge block.
+          if (use_inst->IsBranch()) {
+            assert(back_edge_block_id == 0 &&
+                   "There should only be one back-edge block");
+            back_edge_block_id = ir_context->get_instr_block(use_inst)->id();
+          }
+          // References to the header inside the loop should not be updated
+          return;
+        }
+
+        // If |use_inst| is not a branch or merge instruction, it should not be
+        // changed.
+        if (!use_inst->IsBranch() &&
+            use_inst->opcode() != SpvOpSelectionMerge &&
+            use_inst->opcode() != SpvOpLoopMerge) {
+          return;
+        }
+
+        // Update the reference.
+        use_inst->SetOperand(use_index, {message_.fresh_id()});
+      });
+
+  assert(back_edge_block_id && "The back-edge block should have been found");
+
+  // Make a new block for the preheader.
+  std::unique_ptr<opt::BasicBlock> preheader = MakeUnique<opt::BasicBlock>(
+      std::unique_ptr<opt::Instruction>(new opt::Instruction(
+          ir_context, SpvOpLabel, 0, message_.fresh_id(), {})));
+
+  uint32_t phi_ids_used = 0;
+
+  // Update the OpPhi instructions and, if there is more than one out-of-loop
+  // predecessor, add necessary OpPhi instructions so the preheader.
+  loop_header->ForEachPhiInst([this, &ir_context, &preheader,
+                               &back_edge_block_id,
+                               &phi_ids_used](opt::Instruction* phi_inst) {
+    // The loop header must have at least 2 incoming edges (the back edge, and
+    // at least one from outside the loop).
+    assert(phi_inst->NumInOperands() >= 4);
+
+    if (phi_inst->NumInOperands() == 4) {
+      // There is just one out-of-loop predecessor, so no additional
+      // instructions in the preheader are necessary. The reference to the
+      // original out-of-loop predecessor needs to be updated so that it refers
+      // to the preheader.
+      uint32_t index_of_out_of_loop_pred_id =
+          phi_inst->GetInOperand(1).words[0] == back_edge_block_id ? 3 : 1;
+      phi_inst->SetInOperand(index_of_out_of_loop_pred_id, {preheader->id()});
+    } else {
+      // There is more than one out-of-loop predecessor, so an OpPhi instruction
+      // needs to be added to the preheader, and its value will depend on all
+      // the current out-of-loop predecessors of the header.
+
+      // Get the operand list and the value corresponding to the back-edge
+      // block.
+      std::vector<opt::Operand> preheader_in_operands;
+      uint32_t back_edge_val = 0;
+
+      for (uint32_t i = 0; i < phi_inst->NumInOperands(); i += 2) {
+        // Only add operands if they don't refer to the back-edge block.
+        if (phi_inst->GetInOperand(i + 1).words[0] == back_edge_block_id) {
+          back_edge_val = phi_inst->GetInOperand(i).words[0];
+        } else {
+          preheader_in_operands.push_back(std::move(phi_inst->GetInOperand(i)));
+          preheader_in_operands.push_back(
+              std::move(phi_inst->GetInOperand(i + 1)));
+        }
+      }
+
+      // Add the new instruction to the preheader.
+      uint32_t fresh_phi_id = message_.phi_id(phi_ids_used++);
+
+      // Update id bound.
+      fuzzerutil::UpdateModuleIdBound(ir_context, fresh_phi_id);
+
+      preheader->AddInstruction(std::unique_ptr<opt::Instruction>(
+          new opt::Instruction(ir_context, SpvOpPhi, phi_inst->type_id(),
+                               fresh_phi_id, preheader_in_operands)));
+
+      // Update the OpPhi instruction in the header so that it refers to the
+      // back edge block and the preheader as the predecessors, and it uses the
+      // newly-defined OpPhi in the preheader for the corresponding value.
+      phi_inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {fresh_phi_id}},
+                               {SPV_OPERAND_TYPE_ID, {preheader->id()}},
+                               {SPV_OPERAND_TYPE_ID, {back_edge_val}},
+                               {SPV_OPERAND_TYPE_ID, {back_edge_block_id}}});
+    }
+  });
+
+  // Update id bound.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Add an unconditional branch from the preheader to the header.
+  preheader->AddInstruction(
+      std::unique_ptr<opt::Instruction>(new opt::Instruction(
+          ir_context, SpvOpBranch, 0, 0,
+          std::initializer_list<opt::Operand>{opt::Operand(
+              spv_operand_type_t::SPV_OPERAND_TYPE_ID, {loop_header->id()})})));
+
+  // Insert the preheader in the module.
+  loop_header->GetParent()->InsertBasicBlockBefore(std::move(preheader),
+                                                   loop_header);
+
+  // Invalidate analyses because the structure of the program changed.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddLoopPreheader::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_loop_preheader() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t> TransformationAddLoopPreheader::GetFreshIds()
+    const {
+  std::unordered_set<uint32_t> result = {message_.fresh_id()};
+  for (auto id : message_.phi_id()) {
+    result.insert(id);
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_loop_preheader.h b/source/fuzz/transformation_add_loop_preheader.h
new file mode 100644
index 0000000..05448f3
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_preheader.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_PREHEADER_H
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_PREHEADER_H
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationAddLoopPreheader : public Transformation {
+ public:
+  explicit TransformationAddLoopPreheader(
+      const protobufs::TransformationAddLoopPreheader& message);
+
+  TransformationAddLoopPreheader(uint32_t loop_header_block, uint32_t fresh_id,
+                                 std::vector<uint32_t> phi_id);
+
+  // - |message_.loop_header_block| must be the id of a loop header block in
+  //   the given module.
+  // - |message_.fresh_id| must be an available id.
+  // - |message_.phi_ids| must be a list of available ids.
+  //   It can be empty if the loop header only has one predecessor outside of
+  //   the loop. Otherwise, it must contain at least as many ids as OpPhi
+  //   instructions in the loop header block.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds a preheader block as the unique out-of-loop predecessor of the given
+  // loop header block. All of the existing out-of-loop predecessors of the
+  // header are changed so that they branch to the preheader instead.
+  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::TransformationAddLoopPreheader message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_PREHEADER_H
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
new file mode 100644
index 0000000..45d3fc8
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.cpp
@@ -0,0 +1,453 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+uint32_t kMaxNumOfIterations = 32;
+}
+
+TransformationAddLoopToCreateIntConstantSynonym::
+    TransformationAddLoopToCreateIntConstantSynonym(
+        const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
+            message)
+    : message_(message) {}
+
+TransformationAddLoopToCreateIntConstantSynonym::
+    TransformationAddLoopToCreateIntConstantSynonym(
+        uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id,
+        uint32_t num_iterations_id, uint32_t block_after_loop_id,
+        uint32_t syn_id, uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id,
+        uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id,
+        uint32_t additional_block_id) {
+  message_.set_constant_id(constant_id);
+  message_.set_initial_val_id(initial_val_id);
+  message_.set_step_val_id(step_val_id);
+  message_.set_num_iterations_id(num_iterations_id);
+  message_.set_block_after_loop_id(block_after_loop_id);
+  message_.set_syn_id(syn_id);
+  message_.set_loop_id(loop_id);
+  message_.set_ctr_id(ctr_id);
+  message_.set_temp_id(temp_id);
+  message_.set_eventual_syn_id(eventual_syn_id);
+  message_.set_incremented_ctr_id(incremented_ctr_id);
+  message_.set_cond_id(cond_id);
+  message_.set_additional_block_id(additional_block_id);
+}
+
+bool TransformationAddLoopToCreateIntConstantSynonym::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Check that |message_.constant_id|, |message_.initial_val_id| and
+  // |message_.step_val_id| are existing constants, and that their values are
+  // not irrelevant.
+  auto constant = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.constant_id());
+  auto initial_val = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.initial_val_id());
+  auto step_val = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.step_val_id());
+
+  if (!constant || !initial_val || !step_val) {
+    return false;
+  }
+  if (transformation_context.GetFactManager()->IdIsIrrelevant(
+          message_.constant_id()) ||
+      transformation_context.GetFactManager()->IdIsIrrelevant(
+          message_.initial_val_id()) ||
+      transformation_context.GetFactManager()->IdIsIrrelevant(
+          message_.step_val_id())) {
+    return false;
+  }
+
+  // Check that the type of |constant| is integer scalar or vector with integer
+  // components.
+  if (!constant->AsIntConstant() &&
+      (!constant->AsVectorConstant() ||
+       !constant->type()->AsVector()->element_type()->AsInteger())) {
+    return false;
+  }
+
+  // Check that the component bit width of |constant| is <= 64.
+  // Consider the width of the constant if it is an integer, of a single
+  // component if it is a vector.
+  uint32_t bit_width =
+      constant->AsIntConstant()
+          ? constant->type()->AsInteger()->width()
+          : constant->type()->AsVector()->element_type()->AsInteger()->width();
+  if (bit_width > 64) {
+    return false;
+  }
+
+  auto constant_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.constant_id());
+  auto initial_val_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id());
+  auto step_val_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.step_val_id());
+
+  // Check that |constant|, |initial_val| and |step_val| have the same type,
+  // with possibly different signedness.
+  if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(),
+                                         initial_val_def->type_id()) ||
+      !fuzzerutil::TypesAreEqualUpToSign(ir_context, constant_def->type_id(),
+                                         step_val_def->type_id())) {
+    return false;
+  }
+
+  // |message_.num_iterations_id| must be a non-irrelevant integer constant with
+  // bit width 32.
+  auto num_iterations = ir_context->get_constant_mgr()->FindDeclaredConstant(
+      message_.num_iterations_id());
+
+  if (!num_iterations || !num_iterations->AsIntConstant() ||
+      num_iterations->type()->AsInteger()->width() != 32 ||
+      transformation_context.GetFactManager()->IdIsIrrelevant(
+          message_.num_iterations_id())) {
+    return false;
+  }
+
+  // Check that the number of iterations is > 0 and <= 32.
+  uint32_t num_iterations_value =
+      num_iterations->AsIntConstant()->GetU32BitValue();
+
+  if (num_iterations_value == 0 || num_iterations_value > kMaxNumOfIterations) {
+    return false;
+  }
+
+  // Check that the module contains 32-bit signed integer scalar constants of
+  // value 0 and 1.
+  if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
+                                           {0}, 32, true, false)) {
+    return false;
+  }
+
+  if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
+                                           {1}, 32, true, false)) {
+    return false;
+  }
+
+  // Check that the module contains the Bool type.
+  if (!fuzzerutil::MaybeGetBoolType(ir_context)) {
+    return false;
+  }
+
+  // Check that the equation C = I - S * N is satisfied.
+
+  // Collect the components in vectors (if the constants are scalars, these
+  // vectors will contain the constants themselves).
+  std::vector<const opt::analysis::Constant*> c_components;
+  std::vector<const opt::analysis::Constant*> i_components;
+  std::vector<const opt::analysis::Constant*> s_components;
+  if (constant->AsIntConstant()) {
+    c_components.emplace_back(constant);
+    i_components.emplace_back(initial_val);
+    s_components.emplace_back(step_val);
+  } else {
+    // It is a vector: get all the components.
+    c_components = constant->AsVectorConstant()->GetComponents();
+    i_components = initial_val->AsVectorConstant()->GetComponents();
+    s_components = step_val->AsVectorConstant()->GetComponents();
+  }
+
+  // Check the value of the components satisfy the equation.
+  for (uint32_t i = 0; i < c_components.size(); i++) {
+    // Use 64-bits integers to be able to handle constants of any width <= 64.
+    uint64_t c_value = c_components[i]->AsIntConstant()->GetZeroExtendedValue();
+    uint64_t i_value = i_components[i]->AsIntConstant()->GetZeroExtendedValue();
+    uint64_t s_value = s_components[i]->AsIntConstant()->GetZeroExtendedValue();
+
+    uint64_t result = i_value - s_value * num_iterations_value;
+
+    // Use bit shifts to ignore the first bits in excess (if there are any). By
+    // shifting left, we discard the first |64 - bit_width| bits. By shifting
+    // right, we move the bits back to their correct position.
+    result = (result << (64 - bit_width)) >> (64 - bit_width);
+
+    if (c_value != result) {
+      return false;
+    }
+  }
+
+  // Check that |message_.block_after_loop_id| is the label of a block.
+  auto block =
+      fuzzerutil::MaybeFindBlock(ir_context, message_.block_after_loop_id());
+
+  // Check that the block exists and has a single predecessor.
+  if (!block || ir_context->cfg()->preds(block->id()).size() != 1) {
+    return false;
+  }
+
+  // Check that the block is not dead.  If it is then the new loop would be
+  // dead and the data it computes would be irrelevant, so we would not be able
+  // to make a synonym.
+  if (transformation_context.GetFactManager()->BlockIsDead(block->id())) {
+    return false;
+  }
+
+  // Check that the block is not a merge block.
+  if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
+    return false;
+  }
+
+  // Check that the block is not a continue block.
+  if (ir_context->GetStructuredCFGAnalysis()->IsContinueBlock(block->id())) {
+    return false;
+  }
+
+  // Check that the block is not a loop header.
+  if (block->IsLoopHeader()) {
+    return false;
+  }
+
+  // Check all the fresh ids.
+  std::set<uint32_t> fresh_ids_used;
+  for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(),
+                      message_.temp_id(), message_.eventual_syn_id(),
+                      message_.incremented_ctr_id(), message_.cond_id()}) {
+    if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
+                                                             &fresh_ids_used)) {
+      return false;
+    }
+  }
+
+  // Check the additional block id if it is non-zero.
+  return !message_.additional_block_id() ||
+         CheckIdIsFreshAndNotUsedByThisTransformation(
+             message_.additional_block_id(), ir_context, &fresh_ids_used);
+}
+
+void TransformationAddLoopToCreateIntConstantSynonym::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Find 32-bit signed integer constants 0 and 1.
+  uint32_t const_0_id = fuzzerutil::MaybeGetIntegerConstant(
+      ir_context, *transformation_context, {0}, 32, true, false);
+  auto const_0_def = ir_context->get_def_use_mgr()->GetDef(const_0_id);
+  uint32_t const_1_id = fuzzerutil::MaybeGetIntegerConstant(
+      ir_context, *transformation_context, {1}, 32, true, false);
+
+  // Retrieve the instruction defining the initial value constant.
+  auto initial_val_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.initial_val_id());
+
+  // Retrieve the block before which we want to insert the loop.
+  auto block_after_loop =
+      ir_context->get_instr_block(message_.block_after_loop_id());
+
+  // Find the predecessor of the block.
+  uint32_t pred_id =
+      ir_context->cfg()->preds(message_.block_after_loop_id())[0];
+
+  // Get the id for the last block in the new loop. It will be
+  // |message_.additional_block_id| if this is non_zero, |message_.loop_id|
+  // otherwise.
+  uint32_t last_loop_block_id = message_.additional_block_id()
+                                    ? message_.additional_block_id()
+                                    : message_.loop_id();
+
+  // Create the loop header block.
+  std::unique_ptr<opt::BasicBlock> loop_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.loop_id(),
+          opt::Instruction::OperandList{}));
+
+  // Add OpPhi instructions to retrieve the current value of the counter and of
+  // the temporary variable that will be decreased at each operation.
+  loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpPhi, const_0_def->type_id(), message_.ctr_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {const_0_id}},
+          {SPV_OPERAND_TYPE_ID, {pred_id}},
+          {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+  loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpPhi, initial_val_def->type_id(), message_.temp_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.initial_val_id()}},
+          {SPV_OPERAND_TYPE_ID, {pred_id}},
+          {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+  // Collect the other instructions in a list. These will be added to an
+  // additional block if |message_.additional_block_id| is defined, to the loop
+  // header otherwise.
+  std::vector<std::unique_ptr<opt::Instruction>> other_instructions;
+
+  // Add an instruction to subtract the step value from the temporary value.
+  // The value of this id will converge to the constant in the last iteration.
+  other_instructions.push_back(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpISub, initial_val_def->type_id(),
+      message_.eventual_syn_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.temp_id()}},
+          {SPV_OPERAND_TYPE_ID, {message_.step_val_id()}}}));
+
+  // Add an instruction to increment the counter.
+  other_instructions.push_back(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpIAdd, const_0_def->type_id(),
+      message_.incremented_ctr_id(),
+      opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {message_.ctr_id()}},
+                                    {SPV_OPERAND_TYPE_ID, {const_1_id}}}));
+
+  // Add an instruction to decide whether the condition holds.
+  other_instructions.push_back(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpSLessThan, fuzzerutil::MaybeGetBoolType(ir_context),
+      message_.cond_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.incremented_ctr_id()}},
+          {SPV_OPERAND_TYPE_ID, {message_.num_iterations_id()}}}));
+
+  // Define the OpLoopMerge instruction for the loop header. The merge block is
+  // the existing block, the continue block is the last block in the loop
+  // (either the loop itself or the additional block).
+  std::unique_ptr<opt::Instruction> merge_inst = MakeUnique<opt::Instruction>(
+      ir_context, SpvOpLoopMerge, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}},
+          {SPV_OPERAND_TYPE_LOOP_CONTROL, {SpvLoopControlMaskNone}}});
+
+  // Define a conditional branch instruction, branching to the loop header if
+  // the condition holds, and to the existing block otherwise. This instruction
+  // will be added to the last block in the loop.
+  std::unique_ptr<opt::Instruction> conditional_branch =
+      MakeUnique<opt::Instruction>(
+          ir_context, SpvOpBranchConditional, 0, 0,
+          opt::Instruction::OperandList{
+              {SPV_OPERAND_TYPE_ID, {message_.cond_id()}},
+              {SPV_OPERAND_TYPE_ID, {message_.loop_id()}},
+              {SPV_OPERAND_TYPE_ID, {message_.block_after_loop_id()}}});
+
+  if (message_.additional_block_id()) {
+    // If an id for the additional block is specified, create an additional
+    // block, containing the instructions in the list and a branching
+    // instruction.
+
+    std::unique_ptr<opt::BasicBlock> additional_block =
+        MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpLabel, 0, message_.additional_block_id(),
+            opt::Instruction::OperandList{}));
+
+    for (auto& instruction : other_instructions) {
+      additional_block->AddInstruction(std::move(instruction));
+    }
+
+    additional_block->AddInstruction(std::move(conditional_branch));
+
+    // Add the merge instruction to the header.
+    loop_block->AddInstruction(std::move(merge_inst));
+
+    // Add an unconditional branch from the header to the additional block.
+    loop_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpBranch, 0, 0,
+        opt::Instruction::OperandList{
+            {SPV_OPERAND_TYPE_ID, {message_.additional_block_id()}}}));
+
+    // Insert the two loop blocks before the existing block.
+    block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block),
+                                                          block_after_loop);
+    block_after_loop->GetParent()->InsertBasicBlockBefore(
+        std::move(additional_block), block_after_loop);
+  } else {
+    // If no id for an additional block is specified, the loop will only be made
+    // up of one block, so we need to add all the instructions to it.
+
+    for (auto& instruction : other_instructions) {
+      loop_block->AddInstruction(std::move(instruction));
+    }
+
+    // Add the merge and conditional branch instructions.
+    loop_block->AddInstruction(std::move(merge_inst));
+    loop_block->AddInstruction(std::move(conditional_branch));
+
+    // Insert the header before the existing block.
+    block_after_loop->GetParent()->InsertBasicBlockBefore(std::move(loop_block),
+                                                          block_after_loop);
+  }
+
+  // Update the branching instructions leading to this block.
+  ir_context->get_def_use_mgr()->ForEachUse(
+      message_.block_after_loop_id(),
+      [this](opt::Instruction* instruction, uint32_t operand_index) {
+        assert(instruction->opcode() != SpvOpLoopMerge &&
+               instruction->opcode() != SpvOpSelectionMerge &&
+               "The block should not be referenced by OpLoopMerge or "
+               "OpSelectionMerge, by construction.");
+        // Replace all uses of the label inside branch instructions.
+        if (instruction->opcode() == SpvOpBranch ||
+            instruction->opcode() == SpvOpBranchConditional ||
+            instruction->opcode() == SpvOpSwitch) {
+          instruction->SetOperand(operand_index, {message_.loop_id()});
+        }
+      });
+
+  // Update all the OpPhi instructions in the block after the loop: its
+  // predecessor is now the last block in the loop.
+  block_after_loop->ForEachPhiInst(
+      [last_loop_block_id](opt::Instruction* phi_inst) {
+        // Since the block only had one predecessor, the id of the predecessor
+        // is input operand 1.
+        phi_inst->SetInOperand(1, {last_loop_block_id});
+      });
+
+  // Add a new OpPhi instruction at the beginning of the block after the loop,
+  // defining the synonym of the constant. The type id will be the same as
+  // |message_.initial_value_id|, since this is the value that is decremented in
+  // the loop.
+  block_after_loop->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpPhi, initial_val_def->type_id(), message_.syn_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.eventual_syn_id()}},
+          {SPV_OPERAND_TYPE_ID, {last_loop_block_id}}}));
+
+  // Update the module id bound with all the fresh ids used.
+  for (uint32_t id : {message_.syn_id(), message_.loop_id(), message_.ctr_id(),
+                      message_.temp_id(), message_.eventual_syn_id(),
+                      message_.incremented_ctr_id(), message_.cond_id(),
+                      message_.cond_id(), message_.additional_block_id()}) {
+    fuzzerutil::UpdateModuleIdBound(ir_context, id);
+  }
+
+  // Since we changed the structure of the module, we need to invalidate all the
+  // analyses.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Record that |message_.syn_id| is synonymous with |message_.constant_id|.
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(message_.syn_id(), {}),
+      MakeDataDescriptor(message_.constant_id(), {}));
+}
+
+protobufs::Transformation
+TransformationAddLoopToCreateIntConstantSynonym::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_loop_to_create_int_constant_synonym() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t>
+TransformationAddLoopToCreateIntConstantSynonym::GetFreshIds() const {
+  return {message_.syn_id(),          message_.loop_id(),
+          message_.ctr_id(),          message_.temp_id(),
+          message_.eventual_syn_id(), message_.incremented_ctr_id(),
+          message_.cond_id(),         message_.additional_block_id()};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
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
new file mode 100644
index 0000000..67c3bcd
--- /dev/null
+++ b/source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+class TransformationAddLoopToCreateIntConstantSynonym : public Transformation {
+ public:
+  explicit TransformationAddLoopToCreateIntConstantSynonym(
+      const protobufs::TransformationAddLoopToCreateIntConstantSynonym&
+          message);
+
+  TransformationAddLoopToCreateIntConstantSynonym(
+      uint32_t constant_id, uint32_t initial_val_id, uint32_t step_val_id,
+      uint32_t num_iterations_id, uint32_t block_after_loop_id, uint32_t syn_id,
+      uint32_t loop_id, uint32_t ctr_id, uint32_t temp_id,
+      uint32_t eventual_syn_id, uint32_t incremented_ctr_id, uint32_t cond_id,
+      uint32_t additional_block_id);
+
+  // - |message_.constant_id|, |message_.initial_value_id|,
+  //   |message_.step_val_id| are integer constants (scalar or vectors) with the
+  //   same type (with possibly different signedness, but same bit width, which
+  //   must be <= 64). Let their value be C, I, S respectively.
+  // - |message_.num_iterations_id| is a 32-bit integer scalar constant, with
+  //   value N > 0 and N <= 32.
+  // - The module contains 32-bit signed integer scalar constants of values 0
+  //   and 1.
+  // - The module contains the boolean type.
+  // - C = I - S * N
+  // - |message_.block_after_loop_id| is the label of a block which has a single
+  //   predecessor and which is not a merge block, a continue block or a loop
+  //   header.
+  // - |message_.block_after_loop_id| must not be a dead block.
+  // - |message_.additional_block_id| is either 0 or a valid fresh id, distinct
+  //   from the other fresh ids.
+  // - All of the other parameters are valid fresh ids.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds a loop to the module, defining a synonym of an integer (scalar or
+  // vector) constant. This id is marked as synonym with the original constant.
+  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::TransformationAddLoopToCreateIntConstantSynonym message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_LOOP_TO_CREATE_INT_CONSTANT_SYNONYM_H_
diff --git a/source/fuzz/transformation_add_no_contraction_decoration.cpp b/source/fuzz/transformation_add_no_contraction_decoration.cpp
index 4668534..29a871d 100644
--- a/source/fuzz/transformation_add_no_contraction_decoration.cpp
+++ b/source/fuzz/transformation_add_no_contraction_decoration.cpp
@@ -105,5 +105,10 @@
   }
 }
 
+std::unordered_set<uint32_t>
+TransformationAddNoContractionDecoration::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_no_contraction_decoration.h b/source/fuzz/transformation_add_no_contraction_decoration.h
index 27c3a80..f5a34e8 100644
--- a/source/fuzz/transformation_add_no_contraction_decoration.h
+++ b/source/fuzz/transformation_add_no_contraction_decoration.h
@@ -44,6 +44,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if and only if |opcode| is the opcode of an arithmetic
diff --git a/source/fuzz/transformation_add_opphi_synonym.cpp b/source/fuzz/transformation_add_opphi_synonym.cpp
new file mode 100644
index 0000000..227c433
--- /dev/null
+++ b/source/fuzz/transformation_add_opphi_synonym.cpp
@@ -0,0 +1,204 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_opphi_synonym.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
+    const protobufs::TransformationAddOpPhiSynonym& message)
+    : message_(message) {}
+
+TransformationAddOpPhiSynonym::TransformationAddOpPhiSynonym(
+    uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
+    uint32_t fresh_id) {
+  message_.set_block_id(block_id);
+  *message_.mutable_pred_to_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(preds_to_ids);
+  message_.set_fresh_id(fresh_id);
+}
+
+bool TransformationAddOpPhiSynonym::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Check that |message_.block_id| is a block label id, and that it is not
+  // dead.
+  auto block = fuzzerutil::MaybeFindBlock(ir_context, message_.block_id());
+  if (!block ||
+      transformation_context.GetFactManager()->BlockIsDead(block->id())) {
+    return false;
+  }
+
+  // Check that |message_.fresh_id| is actually fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // Check that |message_.pred_to_id| contains a mapping for all of the block's
+  // predecessors.
+  std::vector<uint32_t> predecessors = ir_context->cfg()->preds(block->id());
+
+  // There must be at least one predecessor.
+  if (predecessors.empty()) {
+    return false;
+  }
+
+  std::map<uint32_t, uint32_t> preds_to_ids =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.pred_to_id());
+
+  // There must not be repeated key values in |message_.pred_to_id|.
+  if (preds_to_ids.size() != static_cast<size_t>(message_.pred_to_id_size())) {
+    return false;
+  }
+
+  // Check that each predecessor has a corresponding mapping and all of the
+  // corresponding ids exist.
+  for (uint32_t pred : predecessors) {
+    if (preds_to_ids.count(pred) == 0) {
+      return false;
+    }
+
+    // Check that the id exists in the module.
+    if (!ir_context->get_def_use_mgr()->GetDef(preds_to_ids[pred])) {
+      return false;
+    }
+  }
+
+  // Get the first id and its type (which should be the same as all the other
+  // ones) and check that the transformation supports this type.
+  uint32_t first_id = preds_to_ids[predecessors[0]];
+  uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
+  if (!CheckTypeIsAllowed(ir_context, type_id)) {
+    return false;
+  }
+
+  // Check that the ids corresponding to predecessors are all synonymous, have
+  // the same type and are available to use at the end of the predecessor.
+  for (uint32_t pred : predecessors) {
+    auto id = preds_to_ids[pred];
+
+    // Check that the id has the same type as the other ones.
+    if (ir_context->get_def_use_mgr()->GetDef(id)->type_id() != type_id) {
+      return false;
+    }
+
+    // Check that the id is synonymous with the others by checking that it is
+    // synonymous with the first one (or it is the same id).
+    if (id != first_id &&
+        !transformation_context.GetFactManager()->IsSynonymous(
+            MakeDataDescriptor(id, {}), MakeDataDescriptor(first_id, {}))) {
+      return false;
+    }
+
+    // Check that the id is available at the end of the corresponding
+    // predecessor block.
+
+    auto pred_block = ir_context->get_instr_block(pred);
+
+    // We should always be able to find the predecessor block, since it is in
+    // the predecessors list of |block|.
+    assert(pred_block && "Could not find one of the predecessor blocks.");
+
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+            ir_context, pred_block->terminator(), id)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationAddOpPhiSynonym::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Get the type id from one of the ids.
+  uint32_t first_id = message_.pred_to_id(0).second();
+  uint32_t type_id = ir_context->get_def_use_mgr()->GetDef(first_id)->type_id();
+
+  // Define the operand list.
+  opt::Instruction::OperandList operand_list;
+
+  // For each predecessor, add the corresponding operands.
+  for (auto& pair : message_.pred_to_id()) {
+    operand_list.emplace_back(
+        opt::Operand{SPV_OPERAND_TYPE_ID, {pair.second()}});
+    operand_list.emplace_back(
+        opt::Operand{SPV_OPERAND_TYPE_ID, {pair.first()}});
+  }
+
+  // Add a new OpPhi instructions at the beginning of the block.
+  ir_context->get_instr_block(message_.block_id())
+      ->begin()
+      .InsertBefore(MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, type_id,
+                                                 message_.fresh_id(),
+                                                 std::move(operand_list)));
+
+  // Update the module id bound.
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // Invalidate all analyses, since we added an instruction to the module.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+
+  // Record the fact that the new id is synonym with the other ones by declaring
+  // that it is a synonym of the first one.
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(message_.fresh_id(), {}),
+      MakeDataDescriptor(first_id, {}));
+}
+
+protobufs::Transformation TransformationAddOpPhiSynonym::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_opphi_synonym() = message_;
+  return result;
+}
+
+bool TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
+    opt::IRContext* ir_context, uint32_t type_id) {
+  auto type = ir_context->get_type_mgr()->GetType(type_id);
+  if (!type) {
+    return false;
+  }
+
+  // We allow the following types: Bool, Integer, Float, Vector, Matrix, Array,
+  // Struct.
+  if (type->AsBool() || type->AsInteger() || type->AsFloat() ||
+      type->AsVector() || type->AsMatrix() || type->AsArray() ||
+      type->AsStruct()) {
+    return true;
+  }
+
+  // We allow pointer types if the VariablePointers capability is enabled and
+  // the pointer has the correct storage class (Workgroup or StorageBuffer).
+  if (type->AsPointer()) {
+    auto storage_class = type->AsPointer()->storage_class();
+    return ir_context->get_feature_mgr()->HasCapability(
+               SpvCapabilityVariablePointers) &&
+           (storage_class == SpvStorageClassWorkgroup ||
+            storage_class == SpvStorageClassStorageBuffer);
+  }
+
+  // We do not allow other types.
+  return false;
+}
+
+std::unordered_set<uint32_t> TransformationAddOpPhiSynonym::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_opphi_synonym.h b/source/fuzz/transformation_add_opphi_synonym.h
new file mode 100644
index 0000000..3b68abe
--- /dev/null
+++ b/source/fuzz/transformation_add_opphi_synonym.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+class TransformationAddOpPhiSynonym : public Transformation {
+ public:
+  explicit TransformationAddOpPhiSynonym(
+      const protobufs::TransformationAddOpPhiSynonym& message);
+
+  TransformationAddOpPhiSynonym(
+      uint32_t block_id, const std::map<uint32_t, uint32_t>& preds_to_ids,
+      uint32_t fresh_id);
+
+  // - |message_.block_id| is the label of a block with at least one
+  //   predecessor.
+  // - |message_.block_id| must not be a dead block.
+  // - |message_.pred_to_id| contains a mapping from each of the predecessors of
+  //   the block to an id that is available at the end of the predecessor.
+  // - All the ids corresponding to a predecessor in |message_.pred_to_id|:
+  //    - have been recorded as synonymous and all have the same type.
+  //      TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3726): if a
+  //       predecessor is a dead block, any id of the right type could be used,
+  //       even if it is not synonym with the others.
+  //    - have one of the following types: Bool, Integer, Float, Vector, Matrix,
+  //      Array, Struct. Pointer types are also allowed if the VariablePointers
+  //      capability is enabled and the storage class is Workgroup or
+  //      StorageBuffer.
+  // - |message_.fresh_id| is a fresh id.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Given a block with n predecessors, with n >= 1, and n corresponding
+  // synonymous ids of the same type, each available to use at the end of the
+  // corresponding predecessor, adds an OpPhi instruction at the beginning of
+  // the block of the form:
+  //   %fresh_id = OpPhi %type %id_1 %pred_1 %id_2 %pred_2 ... %id_n %pred_n
+  // This instruction is then marked as synonymous with the ids.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  // Returns true if |type_id| is the id of a type in the module, which is one
+  // of the following: Bool, Integer, Float, Vector, Matrix, Array, Struct.
+  // Pointer types are also allowed if the VariablePointers capability is
+  // enabled and the storage class is Workgroup or StorageBuffer.
+  static bool CheckTypeIsAllowed(opt::IRContext* ir_context, uint32_t type_id);
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddOpPhiSynonym message_;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_OPPHI_SYNONYM_H_
diff --git a/source/fuzz/transformation_add_parameter.cpp b/source/fuzz/transformation_add_parameter.cpp
index cc32362..9ed0bfb 100644
--- a/source/fuzz/transformation_add_parameter.cpp
+++ b/source/fuzz/transformation_add_parameter.cpp
@@ -14,8 +14,6 @@
 
 #include "source/fuzz/transformation_add_parameter.h"
 
-#include <source/spirv_constant.h>
-
 #include "source/fuzz/fuzzer_util.h"
 
 namespace spvtools {
@@ -26,17 +24,20 @@
     : message_(message) {}
 
 TransformationAddParameter::TransformationAddParameter(
-    uint32_t function_id, uint32_t parameter_fresh_id, uint32_t initializer_id,
+    uint32_t function_id, uint32_t parameter_fresh_id,
+    uint32_t parameter_type_id, std::map<uint32_t, uint32_t> call_parameter_ids,
     uint32_t function_type_fresh_id) {
   message_.set_function_id(function_id);
   message_.set_parameter_fresh_id(parameter_fresh_id);
-  message_.set_initializer_id(initializer_id);
+  message_.set_parameter_type_id(parameter_type_id);
+  *message_.mutable_call_parameter_ids() =
+      fuzzerutil::MapToRepeatedUInt32Pair(call_parameter_ids);
   message_.set_function_type_fresh_id(function_type_fresh_id);
 }
 
 bool TransformationAddParameter::IsApplicable(
     opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
-  // Check that function exists
+  // Check that function exists.
   const auto* function =
       fuzzerutil::FindFunction(ir_context, message_.function_id());
   if (!function ||
@@ -44,22 +45,51 @@
     return false;
   }
 
-  // Check that |initializer_id| is valid.
-  const auto* initializer_inst =
-      ir_context->get_def_use_mgr()->GetDef(message_.initializer_id());
-
-  if (!initializer_inst) {
+  // The type must be supported.
+  if (ir_context->get_def_use_mgr()->GetDef(message_.parameter_type_id()) ==
+      nullptr) {
+    return false;
+  }
+  if (!IsParameterTypeSupported(ir_context, message_.parameter_type_id())) {
     return false;
   }
 
-  // Check that initializer's type is valid.
-  const auto* initializer_type =
-      ir_context->get_type_mgr()->GetType(initializer_inst->type_id());
+  // Iterate over all callers.
+  std::map<uint32_t, uint32_t> call_parameter_ids_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
+  for (auto* instr :
+       fuzzerutil::GetCallers(ir_context, message_.function_id())) {
+    uint32_t caller_id = instr->result_id();
 
-  if (!initializer_type || !IsParameterTypeSupported(*initializer_type)) {
-    return false;
+    // If there is no entry for this caller, return false.
+    if (call_parameter_ids_map.find(caller_id) ==
+        call_parameter_ids_map.end()) {
+      return false;
+    }
+    uint32_t value_id = call_parameter_ids_map[caller_id];
+
+    auto value_instr = ir_context->get_def_use_mgr()->GetDef(value_id);
+    if (!value_instr) {
+      return false;
+    }
+    // If the id of the value of the map is not available before the caller,
+    // return false.
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, instr,
+                                                    value_id)) {
+      return false;
+    }
+
+    // The type of the value must be defined.
+    uint32_t value_type_id = fuzzerutil::GetTypeId(ir_context, value_id);
+    if (!value_type_id) {
+      return false;
+    }
+
+    // Type of every value of the map must be the same for all callers.
+    if (message_.parameter_type_id() != value_type_id) {
+      return false;
+    }
   }
-
   return fuzzerutil::IsFreshId(ir_context, message_.parameter_fresh_id()) &&
          fuzzerutil::IsFreshId(ir_context, message_.function_type_fresh_id()) &&
          message_.parameter_fresh_id() != message_.function_type_fresh_id();
@@ -68,72 +98,72 @@
 void TransformationAddParameter::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  // Find the function that will be transformed
+  // Find the function that will be transformed.
   auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id());
   assert(function && "Can't find the function");
 
-  auto parameter_type_id =
-      fuzzerutil::GetTypeId(ir_context, message_.initializer_id());
-  assert(parameter_type_id != 0 && "Initializer has invalid type");
+  std::map<uint32_t, uint32_t> call_parameter_ids_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
+
+  uint32_t new_parameter_type_id = message_.parameter_type_id();
+  auto new_parameter_type =
+      ir_context->get_type_mgr()->GetType(new_parameter_type_id);
+  assert(new_parameter_type && "New parameter has invalid type.");
 
   // Add new parameters to the function.
   function->AddParameter(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpFunctionParameter, parameter_type_id,
+      ir_context, SpvOpFunctionParameter, new_parameter_type_id,
       message_.parameter_fresh_id(), opt::Instruction::OperandList()));
 
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.parameter_fresh_id());
 
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
-  //  Add an PointeeValueIsIrrelevant fact if the parameter is a pointer.
-
-  // Mark new parameter as irrelevant so that we can replace its use with some
-  // other id.
-  transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
-      message_.parameter_fresh_id());
-
   // Fix all OpFunctionCall instructions.
-  ir_context->get_def_use_mgr()->ForEachUser(
-      &function->DefInst(), [this](opt::Instruction* call) {
-        if (call->opcode() != SpvOpFunctionCall ||
-            call->GetSingleWordInOperand(0) != message_.function_id()) {
-          return;
-        }
+  for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
+    inst->AddOperand(
+        {SPV_OPERAND_TYPE_ID, {call_parameter_ids_map[inst->result_id()]}});
+  }
 
-        call->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}});
-      });
+  // Update function's type.
+  {
+    // We use a separate scope here since |old_function_type| might become a
+    // dangling pointer after the call to the fuzzerutil::UpdateFunctionType.
 
-  auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function);
-  assert(old_function_type && "Function must have a valid type");
+    const auto* old_function_type =
+        fuzzerutil::GetFunctionType(ir_context, function);
+    assert(old_function_type && "Function must have a valid type");
 
-  if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) {
-    // Adjust existing function type if it is used only by this function.
-    old_function_type->AddOperand({SPV_OPERAND_TYPE_ID, {parameter_type_id}});
-
-    // We must make sure that all dependencies of |old_function_type| are
-    // evaluated before |old_function_type| (i.e. the domination rules are not
-    // broken). Thus, we move |old_function_type| to the end of the list of all
-    // types in the module.
-    old_function_type->RemoveFromList();
-    ir_context->AddType(std::unique_ptr<opt::Instruction>(old_function_type));
-  } else {
-    // Otherwise, either create a new type or use an existing one.
-    std::vector<uint32_t> type_ids;
-    type_ids.reserve(old_function_type->NumInOperands() + 1);
-
-    for (uint32_t i = 0, n = old_function_type->NumInOperands(); i < n; ++i) {
-      type_ids.push_back(old_function_type->GetSingleWordInOperand(i));
+    std::vector<uint32_t> parameter_type_ids;
+    for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) {
+      parameter_type_ids.push_back(
+          old_function_type->GetSingleWordInOperand(i));
     }
 
-    type_ids.push_back(parameter_type_id);
+    parameter_type_ids.push_back(new_parameter_type_id);
 
-    function->DefInst().SetInOperand(
-        1, {fuzzerutil::FindOrCreateFunctionType(
-               ir_context, message_.function_type_fresh_id(), type_ids)});
+    fuzzerutil::UpdateFunctionType(
+        ir_context, function->result_id(), message_.function_type_fresh_id(),
+        old_function_type->GetSingleWordInOperand(0), parameter_type_ids);
   }
 
+  auto new_parameter_kind = new_parameter_type->kind();
+
   // Make sure our changes are analyzed.
   ir_context->InvalidateAnalysesExceptFor(
       opt::IRContext::Analysis::kAnalysisNone);
+
+  // If the |new_parameter_type_id| is not a pointer type, mark id as
+  // irrelevant so that we can replace its use with some other id. If the
+  // |new_parameter_type_id| is a pointer type, we cannot mark it with
+  // IdIsIrrelevant, because this pointer might be replaced by a pointer from
+  // original shader. This would change the semantics of the module. In the case
+  // of a pointer type we mark it with PointeeValueIsIrrelevant.
+  if (new_parameter_kind != opt::analysis::Type::kPointer) {
+    transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
+        message_.parameter_fresh_id());
+  } else {
+    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+        message_.parameter_fresh_id());
+  }
 }
 
 protobufs::Transformation TransformationAddParameter::ToMessage() const {
@@ -143,27 +173,53 @@
 }
 
 bool TransformationAddParameter::IsParameterTypeSupported(
-    const opt::analysis::Type& type) {
+    opt::IRContext* ir_context, uint32_t type_id) {
   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
   //  Think about other type instructions we can add here.
-  switch (type.kind()) {
-    case opt::analysis::Type::kBool:
-    case opt::analysis::Type::kInteger:
-    case opt::analysis::Type::kFloat:
-    case opt::analysis::Type::kArray:
-    case opt::analysis::Type::kMatrix:
-    case opt::analysis::Type::kVector:
+  opt::Instruction* type_inst = ir_context->get_def_use_mgr()->GetDef(type_id);
+  switch (type_inst->opcode()) {
+    case SpvOpTypeBool:
+    case SpvOpTypeInt:
+    case SpvOpTypeFloat:
+    case SpvOpTypeMatrix:
+    case SpvOpTypeVector:
       return true;
-    case opt::analysis::Type::kStruct:
-      return std::all_of(type.AsStruct()->element_types().begin(),
-                         type.AsStruct()->element_types().end(),
-                         [](const opt::analysis::Type* element_type) {
-                           return IsParameterTypeSupported(*element_type);
-                         });
+    case SpvOpTypeArray:
+      return IsParameterTypeSupported(ir_context,
+                                      type_inst->GetSingleWordInOperand(0));
+    case SpvOpTypeStruct:
+      if (fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context, type_id)) {
+        return false;
+      }
+      for (uint32_t i = 0; i < type_inst->NumInOperands(); i++) {
+        if (!IsParameterTypeSupported(ir_context,
+                                      type_inst->GetSingleWordInOperand(i))) {
+          return false;
+        }
+      }
+      return true;
+    case SpvOpTypePointer: {
+      SpvStorageClass storage_class =
+          static_cast<SpvStorageClass>(type_inst->GetSingleWordInOperand(0));
+      switch (storage_class) {
+        case SpvStorageClassPrivate:
+        case SpvStorageClassFunction:
+        case SpvStorageClassWorkgroup: {
+          return IsParameterTypeSupported(ir_context,
+                                          type_inst->GetSingleWordInOperand(1));
+        }
+        default:
+          return false;
+      }
+    }
     default:
       return false;
   }
 }
 
+std::unordered_set<uint32_t> TransformationAddParameter::GetFreshIds() const {
+  return {message_.parameter_fresh_id(), message_.function_type_fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_parameter.h b/source/fuzz/transformation_add_parameter.h
index e6b9019..a33521d 100644
--- a/source/fuzz/transformation_add_parameter.h
+++ b/source/fuzz/transformation_add_parameter.h
@@ -29,14 +29,19 @@
       const protobufs::TransformationAddParameter& message);
 
   TransformationAddParameter(uint32_t function_id, uint32_t parameter_fresh_id,
-                             uint32_t initializer_id,
+                             uint32_t parameter_type_id,
+                             std::map<uint32_t, uint32_t> call_parameter_ids,
                              uint32_t function_type_fresh_id);
 
   // - |function_id| must be a valid result id of some non-entry-point function
   //   in the module.
-  // - |initializer_id| must be a valid result id of some instruction in the
-  //   module. Instruction's type must be supported by this transformation
-  //   as specified by IsParameterTypeSupported function.
+  // - |parameter_type_id| is a type id of the new parameter. The type must be
+  //   supported by this transformation as specified by IsParameterTypeSupported
+  //   function.
+  // - |call_parameter_id| must map from every id of an OpFunctionCall
+  //   instruction of this function to the id that will be passed as the new
+  //   parameter at that call site. There could be no callers, therefore this
+  //   map can be empty.
   // - |parameter_fresh_id| and |function_type_fresh_id| are fresh ids and are
   //   not equal.
   bool IsApplicable(
@@ -46,16 +51,19 @@
   // - Creates a new OpFunctionParameter instruction with result id
   //   |parameter_fresh_id| for the function with |function_id|.
   // - Adjusts function's type to include a new parameter.
-  // - Adds |initializer_id| as a new operand to every OpFunctionCall
-  //   instruction that calls the function.
+  // - Adds an argument to every caller of the function to account for the added
+  //   parameter. The argument is the value in |call_parameter_id| map.
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if the type of the parameter is supported by this
   // transformation.
-  static bool IsParameterTypeSupported(const opt::analysis::Type& type);
+  static bool IsParameterTypeSupported(opt::IRContext* ir_context,
+                                       uint32_t type_id);
 
  private:
   protobufs::TransformationAddParameter message_;
diff --git a/source/fuzz/transformation_add_relaxed_decoration.cpp b/source/fuzz/transformation_add_relaxed_decoration.cpp
index effa71d..7b51305 100644
--- a/source/fuzz/transformation_add_relaxed_decoration.cpp
+++ b/source/fuzz/transformation_add_relaxed_decoration.cpp
@@ -142,5 +142,10 @@
   }
 }
 
+std::unordered_set<uint32_t> TransformationAddRelaxedDecoration::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
\ No newline at end of file
diff --git a/source/fuzz/transformation_add_relaxed_decoration.h b/source/fuzz/transformation_add_relaxed_decoration.h
index 30c1abf..3f8bf3e 100644
--- a/source/fuzz/transformation_add_relaxed_decoration.h
+++ b/source/fuzz/transformation_add_relaxed_decoration.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_TRANSFORMATION_ADD_RELAXED_DECORATION_H
-#define SPIRV_TOOLS_TRANSFORMATION_ADD_RELAXED_DECORATION_H
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_RELAXED_DECORATION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_RELAXED_DECORATION_H_
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
@@ -45,6 +45,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if and only if |opcode| is the opcode of an instruction
@@ -59,4 +61,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_TRANSFORMATION_ADD_RELAXED_DECORATION_H
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_RELAXED_DECORATION_H_
diff --git a/source/fuzz/transformation_add_spec_constant_op.cpp b/source/fuzz/transformation_add_spec_constant_op.cpp
index d6a7083..19c5e85 100644
--- a/source/fuzz/transformation_add_spec_constant_op.cpp
+++ b/source/fuzz/transformation_add_spec_constant_op.cpp
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/fuzz/transformation_add_spec_constant_op.h"
+
 #include <utility>
 
 #include "source/fuzz/fuzzer_util.h"
-#include "source/fuzz/transformation_add_spec_constant_op.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -45,7 +46,8 @@
   auto clone = fuzzerutil::CloneIRContext(ir_context);
   ApplyImpl(clone.get());
   return fuzzerutil::IsValid(clone.get(),
-                             transformation_context.GetValidatorOptions());
+                             transformation_context.GetValidatorOptions(),
+                             fuzzerutil::kSilentMessageConsumer);
 }
 
 void TransformationAddSpecConstantOp::Apply(
@@ -80,5 +82,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddSpecConstantOp::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_spec_constant_op.h b/source/fuzz/transformation_add_spec_constant_op.h
index c0f7154..29851fd 100644
--- a/source/fuzz/transformation_add_spec_constant_op.h
+++ b/source/fuzz/transformation_add_spec_constant_op.h
@@ -46,6 +46,8 @@
   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:
diff --git a/source/fuzz/transformation_add_synonym.cpp b/source/fuzz/transformation_add_synonym.cpp
index 6a93e61..a516916 100644
--- a/source/fuzz/transformation_add_synonym.cpp
+++ b/source/fuzz/transformation_add_synonym.cpp
@@ -68,6 +68,17 @@
     return false;
   }
 
+  const auto* insert_before_inst_block =
+      ir_context->get_instr_block(insert_before_inst);
+  assert(insert_before_inst_block &&
+         "|insert_before_inst| must be in some block");
+
+  if (transformation_context.GetFactManager()->BlockIsDead(
+          insert_before_inst_block->id())) {
+    // We don't create synonyms in dead blocks.
+    return false;
+  }
+
   // Check that we can insert |message._synonymous_instruction| before
   // |message_.insert_before| instruction. We use OpIAdd to represent some
   // instruction that can produce a synonym.
@@ -115,7 +126,7 @@
   // Mark two ids as synonymous.
   transformation_context->GetFactManager()->AddFactDataSynonym(
       MakeDataDescriptor(message_.result_id(), {}),
-      MakeDataDescriptor(message_.synonym_fresh_id(), {}), ir_context);
+      MakeDataDescriptor(message_.synonym_fresh_id(), {}));
 }
 
 protobufs::Transformation TransformationAddSynonym::ToMessage() const {
@@ -309,5 +320,9 @@
   }
 }
 
+std::unordered_set<uint32_t> TransformationAddSynonym::GetFreshIds() const {
+  return {message_.synonym_fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_synonym.h b/source/fuzz/transformation_add_synonym.h
index 7705415..33c4353 100644
--- a/source/fuzz/transformation_add_synonym.h
+++ b/source/fuzz/transformation_add_synonym.h
@@ -53,6 +53,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if we can create a synonym of |inst| according to the
diff --git a/source/fuzz/transformation_add_type_array.cpp b/source/fuzz/transformation_add_type_array.cpp
index 8f5af07..c9f6a87 100644
--- a/source/fuzz/transformation_add_type_array.cpp
+++ b/source/fuzz/transformation_add_type_array.cpp
@@ -84,5 +84,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeArray::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_array.h b/source/fuzz/transformation_add_type_array.h
index 5e9b8aa..ebefc23 100644
--- a/source/fuzz/transformation_add_type_array.h
+++ b/source/fuzz/transformation_add_type_array.h
@@ -45,6 +45,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_boolean.cpp b/source/fuzz/transformation_add_type_boolean.cpp
index 77409a8..ebbfabc 100644
--- a/source/fuzz/transformation_add_type_boolean.cpp
+++ b/source/fuzz/transformation_add_type_boolean.cpp
@@ -57,5 +57,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeBoolean::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_boolean.h b/source/fuzz/transformation_add_type_boolean.h
index 5ce5b9a..25c9272 100644
--- a/source/fuzz/transformation_add_type_boolean.h
+++ b/source/fuzz/transformation_add_type_boolean.h
@@ -39,6 +39,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_float.cpp b/source/fuzz/transformation_add_type_float.cpp
index c0c434b..da3f3e4 100644
--- a/source/fuzz/transformation_add_type_float.cpp
+++ b/source/fuzz/transformation_add_type_float.cpp
@@ -36,6 +36,28 @@
     return false;
   }
 
+  // Checks float type width capabilities.
+  switch (message_.width()) {
+    case 16:
+      // The Float16 capability must be present.
+      if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat16)) {
+        return false;
+      }
+      break;
+    case 32:
+      // No capabilities needed.
+      break;
+    case 64:
+      // The Float64 capability must be present.
+      if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityFloat64)) {
+        return false;
+      }
+      break;
+    default:
+      assert(false && "Unexpected float type width");
+      return false;
+  }
+
   // Applicable if there is no float type with this width already declared in
   // the module.
   return fuzzerutil::MaybeGetFloatType(ir_context, message_.width()) == 0;
@@ -56,5 +78,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeFloat::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_float.h b/source/fuzz/transformation_add_type_float.h
index a8fa0e1..30cd0fc 100644
--- a/source/fuzz/transformation_add_type_float.h
+++ b/source/fuzz/transformation_add_type_float.h
@@ -41,6 +41,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_function.cpp b/source/fuzz/transformation_add_type_function.cpp
index c878025..b6cddfc 100644
--- a/source/fuzz/transformation_add_type_function.cpp
+++ b/source/fuzz/transformation_add_type_function.cpp
@@ -80,5 +80,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeFunction::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_function.h b/source/fuzz/transformation_add_type_function.h
index f26b250..59ded2b 100644
--- a/source/fuzz/transformation_add_type_function.h
+++ b/source/fuzz/transformation_add_type_function.h
@@ -49,6 +49,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_int.cpp b/source/fuzz/transformation_add_type_int.cpp
index 20759fc..253ea15 100644
--- a/source/fuzz/transformation_add_type_int.cpp
+++ b/source/fuzz/transformation_add_type_int.cpp
@@ -38,6 +38,34 @@
     return false;
   }
 
+  // Checks integer type width capabilities.
+  switch (message_.width()) {
+    case 8:
+      // The Int8 capability must be present.
+      if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt8)) {
+        return false;
+      }
+      break;
+    case 16:
+      // The Int16 capability must be present.
+      if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt16)) {
+        return false;
+      }
+      break;
+    case 32:
+      // No capabilities needed.
+      break;
+    case 64:
+      // The Int64 capability must be present.
+      if (!ir_context->get_feature_mgr()->HasCapability(SpvCapabilityInt64)) {
+        return false;
+      }
+      break;
+    default:
+      assert(false && "Unexpected integer type width");
+      return false;
+  }
+
   // Applicable if there is no int type with this width and signedness already
   // declared in the module.
   return fuzzerutil::MaybeGetIntegerType(ir_context, message_.width(),
@@ -60,5 +88,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeInt::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_int.h b/source/fuzz/transformation_add_type_int.h
index 5c3c959..20c90ca 100644
--- a/source/fuzz/transformation_add_type_int.h
+++ b/source/fuzz/transformation_add_type_int.h
@@ -42,6 +42,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_matrix.cpp b/source/fuzz/transformation_add_type_matrix.cpp
index 2c24eaa..cecebb4 100644
--- a/source/fuzz/transformation_add_type_matrix.cpp
+++ b/source/fuzz/transformation_add_type_matrix.cpp
@@ -67,5 +67,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeMatrix::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_matrix.h b/source/fuzz/transformation_add_type_matrix.h
index 6d0724e..f1d4165 100644
--- a/source/fuzz/transformation_add_type_matrix.h
+++ b/source/fuzz/transformation_add_type_matrix.h
@@ -43,6 +43,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_pointer.cpp b/source/fuzz/transformation_add_type_pointer.cpp
index 6cc8171..f74768d 100644
--- a/source/fuzz/transformation_add_type_pointer.cpp
+++ b/source/fuzz/transformation_add_type_pointer.cpp
@@ -62,5 +62,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypePointer::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_pointer.h b/source/fuzz/transformation_add_type_pointer.h
index 3b50a29..3f686e9 100644
--- a/source/fuzz/transformation_add_type_pointer.h
+++ b/source/fuzz/transformation_add_type_pointer.h
@@ -43,6 +43,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_struct.cpp b/source/fuzz/transformation_add_type_struct.cpp
index a7345a1..b20ffb0 100644
--- a/source/fuzz/transformation_add_type_struct.cpp
+++ b/source/fuzz/transformation_add_type_struct.cpp
@@ -44,6 +44,14 @@
       // function type; both are illegal.
       return false;
     }
+
+    // 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.
+    if (type->AsStruct() &&
+        fuzzerutil::MembersHaveBuiltInDecoration(ir_context, member_type)) {
+      return false;
+    }
   }
   return true;
 }
@@ -66,5 +74,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeStruct::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_struct.h b/source/fuzz/transformation_add_type_struct.h
index 86a532d..94be42a 100644
--- a/source/fuzz/transformation_add_type_struct.h
+++ b/source/fuzz/transformation_add_type_struct.h
@@ -35,6 +35,9 @@
 
   // - |message_.fresh_id| must be a fresh id
   // - |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).
   bool IsApplicable(
       opt::IRContext* ir_context,
       const TransformationContext& transformation_context) const override;
@@ -44,6 +47,8 @@
   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:
diff --git a/source/fuzz/transformation_add_type_vector.cpp b/source/fuzz/transformation_add_type_vector.cpp
index 10a6224..a3b0010 100644
--- a/source/fuzz/transformation_add_type_vector.cpp
+++ b/source/fuzz/transformation_add_type_vector.cpp
@@ -61,5 +61,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAddTypeVector::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_add_type_vector.h b/source/fuzz/transformation_add_type_vector.h
index 240f7cc..c25d565 100644
--- a/source/fuzz/transformation_add_type_vector.h
+++ b/source/fuzz/transformation_add_type_vector.h
@@ -43,6 +43,8 @@
   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:
diff --git a/source/fuzz/transformation_adjust_branch_weights.cpp b/source/fuzz/transformation_adjust_branch_weights.cpp
index ed68134..8b74ed3 100644
--- a/source/fuzz/transformation_adjust_branch_weights.cpp
+++ b/source/fuzz/transformation_adjust_branch_weights.cpp
@@ -93,5 +93,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationAdjustBranchWeights::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_adjust_branch_weights.h b/source/fuzz/transformation_adjust_branch_weights.h
index 638b0a9..4d451a5 100644
--- a/source/fuzz/transformation_adjust_branch_weights.h
+++ b/source/fuzz/transformation_adjust_branch_weights.h
@@ -45,6 +45,8 @@
   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:
diff --git a/source/fuzz/transformation_composite_construct.cpp b/source/fuzz/transformation_composite_construct.cpp
index 15af53e..f6711f5 100644
--- a/source/fuzz/transformation_composite_construct.cpp
+++ b/source/fuzz/transformation_composite_construct.cpp
@@ -40,8 +40,7 @@
 }
 
 bool TransformationCompositeConstruct::IsApplicable(
-    opt::IRContext* ir_context,
-    const TransformationContext& transformation_context) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     // We require the id for the composite constructor to be unused.
     return false;
@@ -94,14 +93,6 @@
       return false;
     }
 
-    // We should be able to create a synonym of |component| if it's not
-    // irrelevant.
-    if (!transformation_context.GetFactManager()->IdIsIrrelevant(component) &&
-        !fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
-                                      inst)) {
-      return false;
-    }
-
     if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before,
                                                     component)) {
       return false;
@@ -136,48 +127,7 @@
   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 
-  // Inform the fact manager that we now have new synonyms: every component of
-  // the composite is synonymous with the id used to construct that component,
-  // except in the case of a vector where a single vector id can span multiple
-  // components.
-  auto composite_type =
-      ir_context->get_type_mgr()->GetType(message_.composite_type_id());
-  uint32_t index = 0;
-  for (auto component : message_.component()) {
-    auto component_type = ir_context->get_type_mgr()->GetType(
-        ir_context->get_def_use_mgr()->GetDef(component)->type_id());
-    if (composite_type->AsVector() && component_type->AsVector()) {
-      // The case where the composite being constructed is a vector and the
-      // component provided for construction is also a vector is special.  It
-      // requires adding a synonym fact relating each element of the sub-vector
-      // to the corresponding element of the composite being constructed.
-      assert(component_type->AsVector()->element_type() ==
-             composite_type->AsVector()->element_type());
-      assert(component_type->AsVector()->element_count() <
-             composite_type->AsVector()->element_count());
-      for (uint32_t subvector_index = 0;
-           subvector_index < component_type->AsVector()->element_count();
-           subvector_index++) {
-        if (!transformation_context->GetFactManager()->IdIsIrrelevant(
-                component)) {
-          transformation_context->GetFactManager()->AddFactDataSynonym(
-              MakeDataDescriptor(component, {subvector_index}),
-              MakeDataDescriptor(message_.fresh_id(), {index}), ir_context);
-        }
-        index++;
-      }
-    } else {
-      // The other cases are simple: the component is made directly synonymous
-      // with the element of the composite being constructed.
-      if (!transformation_context->GetFactManager()->IdIsIrrelevant(
-              component)) {
-        transformation_context->GetFactManager()->AddFactDataSynonym(
-            MakeDataDescriptor(component, {}),
-            MakeDataDescriptor(message_.fresh_id(), {index}), ir_context);
-      }
-      index++;
-    }
-  }
+  AddDataSynonymFacts(ir_context, transformation_context);
 }
 
 bool TransformationCompositeConstruct::ComponentsForArrayConstructionAreOK(
@@ -310,5 +260,64 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationCompositeConstruct::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
+void TransformationCompositeConstruct::AddDataSynonymFacts(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // If the result id of the composite we are constructing is irrelevant (e.g.
+  // because it is in a dead block) then we do not make any synonyms.
+  if (transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.fresh_id())) {
+    return;
+  }
+
+  // Inform the fact manager that we now have new synonyms: every component of
+  // the composite is synonymous with the id used to construct that component
+  // (so long as it is legitimate to create a synonym from that id), except in
+  // the case of a vector where a single vector id can span multiple components.
+  auto composite_type =
+      ir_context->get_type_mgr()->GetType(message_.composite_type_id());
+  uint32_t index = 0;
+  for (auto component : message_.component()) {
+    if (!fuzzerutil::CanMakeSynonymOf(
+            ir_context, *transformation_context,
+            ir_context->get_def_use_mgr()->GetDef(component))) {
+      index++;
+      continue;
+    }
+    auto component_type = ir_context->get_type_mgr()->GetType(
+        ir_context->get_def_use_mgr()->GetDef(component)->type_id());
+    if (composite_type->AsVector() && component_type->AsVector()) {
+      // The case where the composite being constructed is a vector and the
+      // component provided for construction is also a vector is special.  It
+      // requires adding a synonym fact relating each element of the sub-vector
+      // to the corresponding element of the composite being constructed.
+      assert(component_type->AsVector()->element_type() ==
+             composite_type->AsVector()->element_type());
+      assert(component_type->AsVector()->element_count() <
+             composite_type->AsVector()->element_count());
+      for (uint32_t subvector_index = 0;
+           subvector_index < component_type->AsVector()->element_count();
+           subvector_index++) {
+        transformation_context->GetFactManager()->AddFactDataSynonym(
+            MakeDataDescriptor(component, {subvector_index}),
+            MakeDataDescriptor(message_.fresh_id(), {index}));
+        index++;
+      }
+    } else {
+      // The other cases are simple: the component is made directly synonymous
+      // with the element of the composite being constructed.
+      transformation_context->GetFactManager()->AddFactDataSynonym(
+          MakeDataDescriptor(component, {}),
+          MakeDataDescriptor(message_.fresh_id(), {index}));
+      index++;
+    }
+  }
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_composite_construct.h b/source/fuzz/transformation_composite_construct.h
index 2e55e70..3a3e43c 100644
--- a/source/fuzz/transformation_composite_construct.h
+++ b/source/fuzz/transformation_composite_construct.h
@@ -58,9 +58,15 @@
   // |message_.base_instruction_id| and |message_.offset|.  The instruction
   // creates a composite of type |message_.composite_type_id| using the ids of
   // |message_.component|.
+  //
+  // Synonym facts are added between the elements of the resulting composite
+  // and the components used to construct it, as long as the associated ids
+  // support synonym creation.
   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:
@@ -84,6 +90,11 @@
       opt::IRContext* ir_context,
       const opt::analysis::Vector& vector_type) const;
 
+  // Helper method for adding data synonym facts when applying the
+  // transformation to |ir_context| and |transformation_context|.
+  void AddDataSynonymFacts(opt::IRContext* ir_context,
+                           TransformationContext* transformation_context) const;
+
   protobufs::TransformationCompositeConstruct message_;
 };
 
diff --git a/source/fuzz/transformation_composite_extract.cpp b/source/fuzz/transformation_composite_extract.cpp
index 9f4d554..2aff02f 100644
--- a/source/fuzz/transformation_composite_extract.cpp
+++ b/source/fuzz/transformation_composite_extract.cpp
@@ -29,7 +29,8 @@
 
 TransformationCompositeExtract::TransformationCompositeExtract(
     const protobufs::InstructionDescriptor& instruction_to_insert_before,
-    uint32_t fresh_id, uint32_t composite_id, std::vector<uint32_t>&& index) {
+    uint32_t fresh_id, uint32_t composite_id,
+    const std::vector<uint32_t>& index) {
   *message_.mutable_instruction_to_insert_before() =
       instruction_to_insert_before;
   message_.set_fresh_id(fresh_id);
@@ -40,8 +41,7 @@
 }
 
 bool TransformationCompositeExtract::IsApplicable(
-    opt::IRContext* ir_context,
-    const TransformationContext& transformation_context) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
   }
@@ -55,27 +55,14 @@
   if (!composite_instruction) {
     return false;
   }
-  if (!transformation_context.GetFactManager()->IdIsIrrelevant(
-          message_.composite_id()) &&
-      !fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
-                                    composite_instruction)) {
-    // |composite_id| will participate in DataSynonym facts. Thus, it can't be
-    // an irrelevant id.
+  if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+          ir_context, instruction_to_insert_before, message_.composite_id())) {
     return false;
   }
-  if (auto block = ir_context->get_instr_block(composite_instruction)) {
-    if (composite_instruction == instruction_to_insert_before ||
-        !ir_context->GetDominatorAnalysis(block->GetParent())
-             ->Dominates(composite_instruction, instruction_to_insert_before)) {
-      return false;
-    }
-  }
-  assert(composite_instruction->type_id() &&
-         "An instruction in a block cannot have a result id but no type id.");
 
   auto composite_type =
       ir_context->get_type_mgr()->GetType(composite_instruction->type_id());
-  if (!composite_type) {
+  if (!fuzzerutil::IsCompositeType(composite_type)) {
     return false;
   }
 
@@ -112,22 +99,7 @@
   ir_context->InvalidateAnalysesExceptFor(
       opt::IRContext::Analysis::kAnalysisNone);
 
-  // Add the fact that the id storing the extracted element is synonymous with
-  // the index into the structure.
-  if (!transformation_context->GetFactManager()->IdIsIrrelevant(
-          message_.composite_id())) {
-    std::vector<uint32_t> indices;
-    for (auto an_index : message_.index()) {
-      indices.push_back(an_index);
-    }
-    protobufs::DataDescriptor data_descriptor_for_extracted_element =
-        MakeDataDescriptor(message_.composite_id(), std::move(indices));
-    protobufs::DataDescriptor data_descriptor_for_result_id =
-        MakeDataDescriptor(message_.fresh_id(), {});
-    transformation_context->GetFactManager()->AddFactDataSynonym(
-        data_descriptor_for_extracted_element, data_descriptor_for_result_id,
-        ir_context);
-  }
+  AddDataSynonymFacts(ir_context, transformation_context);
 }
 
 protobufs::Transformation TransformationCompositeExtract::ToMessage() const {
@@ -136,5 +108,35 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationCompositeExtract::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
+void TransformationCompositeExtract::AddDataSynonymFacts(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Don't add synonyms if the composite being extracted from is not suitable,
+  // 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())) ||
+      transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.fresh_id())) {
+    return;
+  }
+
+  // Add the fact that the id storing the extracted element is synonymous with
+  // the index into the structure.
+  std::vector<uint32_t> indices(message_.index().begin(),
+                                message_.index().end());
+  auto data_descriptor_for_extracted_element =
+      MakeDataDescriptor(message_.composite_id(), indices);
+  auto data_descriptor_for_result_id =
+      MakeDataDescriptor(message_.fresh_id(), {});
+  transformation_context->GetFactManager()->AddFactDataSynonym(
+      data_descriptor_for_extracted_element, data_descriptor_for_result_id);
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_composite_extract.h b/source/fuzz/transformation_composite_extract.h
index 34df823..0f5348a 100644
--- a/source/fuzz/transformation_composite_extract.h
+++ b/source/fuzz/transformation_composite_extract.h
@@ -30,7 +30,8 @@
 
   TransformationCompositeExtract(
       const protobufs::InstructionDescriptor& instruction_to_insert_before,
-      uint32_t fresh_id, uint32_t composite_id, std::vector<uint32_t>&& index);
+      uint32_t fresh_id, uint32_t composite_id,
+      const std::vector<uint32_t>& index);
 
   // - |message_.fresh_id| must be available
   // - |message_.instruction_to_insert_before| must identify an instruction
@@ -48,15 +49,24 @@
   // Adds an OpCompositeConstruct instruction before the instruction identified
   // by |message_.instruction_to_insert_before|, that extracts from
   // |message_.composite_id| via indices |message_.index| into
-  // |message_.fresh_id|. If |composite_id| is not an irrelevant id,
-  // generates a data synonym fact relating
-  // |message_.fresh_id| to the extracted element.
+  // |message_.fresh_id|.
+  //
+  // Adds a synonym fact associating |message_.fresh_id| with the relevant
+  // element of |message_.composite_id|, as long as these ids support synonym
+  // creation.
   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:
+  // Helper method for adding data synonym facts when applying the
+  // transformation to |ir_context| and |transformation_context|.
+  void AddDataSynonymFacts(opt::IRContext* ir_context,
+                           TransformationContext* transformation_context) const;
+
   protobufs::TransformationCompositeExtract message_;
 };
 
diff --git a/source/fuzz/transformation_composite_insert.cpp b/source/fuzz/transformation_composite_insert.cpp
new file mode 100644
index 0000000..cc68141
--- /dev/null
+++ b/source/fuzz/transformation_composite_insert.cpp
@@ -0,0 +1,241 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 "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"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationCompositeInsert::TransformationCompositeInsert(
+    const spvtools::fuzz::protobufs::TransformationCompositeInsert& message)
+    : message_(message) {}
+
+TransformationCompositeInsert::TransformationCompositeInsert(
+    const protobufs::InstructionDescriptor& instruction_to_insert_before,
+    uint32_t fresh_id, uint32_t composite_id, uint32_t object_id,
+    const std::vector<uint32_t>& index) {
+  *message_.mutable_instruction_to_insert_before() =
+      instruction_to_insert_before;
+  message_.set_fresh_id(fresh_id);
+  message_.set_composite_id(composite_id);
+  message_.set_object_id(object_id);
+  for (auto an_index : index) {
+    message_.add_index(an_index);
+  }
+}
+
+bool TransformationCompositeInsert::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // |message_.fresh_id| must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // |message_.composite_id| must refer to an existing composite value.
+  auto composite =
+      ir_context->get_def_use_mgr()->GetDef(message_.composite_id());
+
+  if (!IsCompositeInstructionSupported(ir_context, composite)) {
+    return false;
+  }
+
+  // The indices in |message_.index| must be suitable for indexing into
+  // |composite->type_id()|.
+  auto component_to_be_replaced_type_id = fuzzerutil::WalkCompositeTypeIndices(
+      ir_context, composite->type_id(), message_.index());
+  if (component_to_be_replaced_type_id == 0) {
+    return false;
+  }
+
+  // The instruction having the id of |message_.object_id| must be defined.
+  auto object_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.object_id());
+  if (object_instruction == nullptr || object_instruction->type_id() == 0) {
+    return false;
+  }
+
+  // We ignore pointers for now.
+  auto object_instruction_type =
+      ir_context->get_type_mgr()->GetType(object_instruction->type_id());
+  if (object_instruction_type->AsPointer() != nullptr) {
+    return false;
+  }
+
+  // The type id of the object having |message_.object_id| and the type id of
+  // the component of the composite at index |message_.index| must be the same.
+  if (component_to_be_replaced_type_id != object_instruction->type_id()) {
+    return false;
+  }
+
+  // |message_.instruction_to_insert_before| must be a defined instruction.
+  auto instruction_to_insert_before =
+      FindInstruction(message_.instruction_to_insert_before(), ir_context);
+  if (instruction_to_insert_before == nullptr) {
+    return false;
+  }
+
+  // |message_.composite_id| and |message_.object_id| must be available before
+  // the |message_.instruction_to_insert_before|.
+  if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+          ir_context, instruction_to_insert_before, message_.composite_id())) {
+    return false;
+  }
+  if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+          ir_context, instruction_to_insert_before, message_.object_id())) {
+    return false;
+  }
+
+  // It must be possible to insert an OpCompositeInsert before this
+  // instruction.
+  return fuzzerutil::CanInsertOpcodeBeforeInstruction(
+      SpvOpCompositeInsert, instruction_to_insert_before);
+}
+
+void TransformationCompositeInsert::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // |message_.struct_fresh_id| must be fresh.
+  assert(fuzzerutil::IsFreshId(ir_context, message_.fresh_id()) &&
+         "|message_.fresh_id| must be fresh");
+
+  std::vector<uint32_t> index =
+      fuzzerutil::RepeatedFieldToVector(message_.index());
+  opt::Instruction::OperandList in_operands;
+  in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.object_id()}});
+  in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.composite_id()}});
+  for (auto i : index) {
+    in_operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}});
+  }
+  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)));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+
+  // We have modified the module so most analyzes are now invalid.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Add data synonym facts that arise from the insertion.
+  AddDataSynonymFacts(ir_context, transformation_context);
+}
+
+protobufs::Transformation TransformationCompositeInsert::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_composite_insert() = message_;
+  return result;
+}
+
+bool TransformationCompositeInsert::IsCompositeInstructionSupported(
+    opt::IRContext* ir_context, opt::Instruction* instruction) {
+  if (instruction == nullptr) {
+    return false;
+  }
+  if (instruction->result_id() == 0 || instruction->type_id() == 0) {
+    return false;
+  }
+  auto composite_type =
+      ir_context->get_type_mgr()->GetType(instruction->type_id());
+  if (!fuzzerutil::IsCompositeType(composite_type)) {
+    return false;
+  }
+
+  // Empty composites are not supported.
+  auto instruction_type_inst =
+      ir_context->get_def_use_mgr()->GetDef(instruction->type_id());
+  if (fuzzerutil::GetBoundForCompositeIndex(*instruction_type_inst,
+                                            ir_context) == 0) {
+    return false;
+  }
+  return true;
+}
+
+std::unordered_set<uint32_t> TransformationCompositeInsert::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
+void TransformationCompositeInsert::AddDataSynonymFacts(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // If the result id arising from the insertion is irrelevant then do not add
+  // any data synonym facts.  (The result id can be irrelevant if the insertion
+  // occurs in a dead block.)
+  if (transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.fresh_id())) {
+    return;
+  }
+
+  // So long as the |message_.composite_id| is suitable for participating in
+  // synonyms, every every element of the insertion result except for at the
+  // index being inserted into is synonymous with the corresponding element of
+  // |message_.composite_id|.  In that case, for every index that is a prefix of
+  // |index|, the components different from the one that contains the inserted
+  // object are synonymous with corresponding elements in the original
+  // composite.
+  uint32_t current_node_type_id =
+      fuzzerutil::GetTypeId(ir_context, message_.composite_id());
+  std::vector<uint32_t> current_index;
+
+  std::vector<uint32_t> index =
+      fuzzerutil::RepeatedFieldToVector(message_.index());
+
+  for (uint32_t current_level : index) {
+    auto current_node_type_inst =
+        ir_context->get_def_use_mgr()->GetDef(current_node_type_id);
+    uint32_t index_to_skip = current_level;
+    uint32_t num_of_components = fuzzerutil::GetBoundForCompositeIndex(
+        *current_node_type_inst, ir_context);
+
+    // Update the current_node_type_id.
+    current_node_type_id = fuzzerutil::WalkOneCompositeTypeIndex(
+        ir_context, current_node_type_id, index_to_skip);
+
+    for (uint32_t i = 0; i < num_of_components; i++) {
+      if (i == index_to_skip) {
+        continue;
+      }
+      current_index.push_back(i);
+      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));
+      }
+      current_index.pop_back();
+    }
+    // Store the prefix of the |index|.
+    current_index.push_back(current_level);
+  }
+  // If the object being inserted supports synonym creation then it is
+  // 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()))) {
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(message_.object_id(), {}),
+        MakeDataDescriptor(message_.fresh_id(), index));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_composite_insert.h b/source/fuzz/transformation_composite_insert.h
new file mode 100644
index 0000000..f229014
--- /dev/null
+++ b/source/fuzz/transformation_composite_insert.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_COMPOSITE_INSERT_H_
+#define SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_INSERT_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 TransformationCompositeInsert : public Transformation {
+ public:
+  explicit TransformationCompositeInsert(
+      const protobufs::TransformationCompositeInsert& message);
+
+  TransformationCompositeInsert(
+      const protobufs::InstructionDescriptor& instruction_to_insert_before,
+      uint32_t fresh_id, uint32_t composite_id, uint32_t object_id,
+      const std::vector<uint32_t>& index);
+
+  // - |message_.fresh_id| must be fresh.
+  // - |message_.composite_id| must refer to an existing composite value.
+  // - |message_.index| must refer to a correct index in the composite.
+  // - The type id of the object and the type id of the component of the
+  //   composite at index |message_.index| must be the same.
+  // - |message_.instruction_to_insert_before| must refer to a defined
+  //   instruction.
+  // - It must be possible to insert OpCompositeInsert before
+  //   |instruction_to_insert_before|.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds an instruction OpCompositeInsert before
+  // |instruction_to_insert_before|, which creates a new composite from
+  // |composite_id| by inserting |object_id| at the specified |index|.
+  // Synonyms are created between those components which are identical in the
+  // original and the modified composite and between the inserted object and its
+  // copy in the modified composite.
+  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 if |instruction| is a instruction of a composite type supported by
+  // this transformation.
+  static bool IsCompositeInstructionSupported(opt::IRContext* ir_context,
+                                              opt::Instruction* instruction);
+
+ private:
+  // Helper method for adding data synonym facts when applying the
+  // transformation to |ir_context| and |transformation_context|.
+  void AddDataSynonymFacts(opt::IRContext* ir_context,
+                           TransformationContext* transformation_context) const;
+
+  protobufs::TransformationCompositeInsert message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_COMPOSITE_INSERT_H_
diff --git a/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp b/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp
index ff3ba3c..f727052 100644
--- a/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp
+++ b/source/fuzz/transformation_compute_data_synonym_fact_closure.cpp
@@ -35,10 +35,10 @@
 }
 
 void TransformationComputeDataSynonymFactClosure::Apply(
-    opt::IRContext* ir_context,
+    opt::IRContext* /*unused*/,
     TransformationContext* transformation_context) const {
   transformation_context->GetFactManager()->ComputeClosureOfFacts(
-      ir_context, message_.maximum_equivalence_class_size());
+      message_.maximum_equivalence_class_size());
 }
 
 protobufs::Transformation
@@ -48,5 +48,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationComputeDataSynonymFactClosure::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_compute_data_synonym_fact_closure.h b/source/fuzz/transformation_compute_data_synonym_fact_closure.h
index eab43ff..dedabe2 100644
--- a/source/fuzz/transformation_compute_data_synonym_fact_closure.h
+++ b/source/fuzz/transformation_compute_data_synonym_fact_closure.h
@@ -41,6 +41,8 @@
   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:
diff --git a/source/fuzz/transformation_context.cpp b/source/fuzz/transformation_context.cpp
index 6c2dfdf..d008ba9 100644
--- a/source/fuzz/transformation_context.cpp
+++ b/source/fuzz/transformation_context.cpp
@@ -14,12 +14,53 @@
 
 #include "source/fuzz/transformation_context.h"
 
+#include <cassert>
+
+#include "source/util/make_unique.h"
+
 namespace spvtools {
 namespace fuzz {
+namespace {
+
+// An overflow id source that should never be used: its methods assert false.
+// This is the right id source for use during fuzzing, when overflow ids should
+// never be required.
+class NullOverflowIdSource : public OverflowIdSource {
+  bool HasOverflowIds() const override {
+    assert(false && "Bad attempt to query whether overflow ids are available.");
+    return false;
+  }
+
+  uint32_t GetNextOverflowId() override {
+    assert(false && "Bad attempt to request an overflow id.");
+    return 0;
+  }
+
+  const std::unordered_set<uint32_t>& GetIssuedOverflowIds() const override {
+    assert(false && "Operation not supported.");
+    return placeholder_;
+  }
+
+ private:
+  std::unordered_set<uint32_t> placeholder_;
+};
+
+}  // namespace
 
 TransformationContext::TransformationContext(
-    FactManager* fact_manager, spv_validator_options validator_options)
-    : fact_manager_(fact_manager), validator_options_(validator_options) {}
+    std::unique_ptr<FactManager> fact_manager,
+    spv_validator_options validator_options)
+    : fact_manager_(std::move(fact_manager)),
+      validator_options_(validator_options),
+      overflow_id_source_(MakeUnique<NullOverflowIdSource>()) {}
+
+TransformationContext::TransformationContext(
+    std::unique_ptr<FactManager> fact_manager,
+    spv_validator_options validator_options,
+    std::unique_ptr<OverflowIdSource> overflow_id_source)
+    : fact_manager_(std::move(fact_manager)),
+      validator_options_(validator_options),
+      overflow_id_source_(std::move(overflow_id_source)) {}
 
 TransformationContext::~TransformationContext() = default;
 
diff --git a/source/fuzz/transformation_context.h b/source/fuzz/transformation_context.h
index 37e15a2..3edcb63 100644
--- a/source/fuzz/transformation_context.h
+++ b/source/fuzz/transformation_context.h
@@ -15,7 +15,10 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
 
-#include "source/fuzz/fact_manager.h"
+#include <memory>
+
+#include "source/fuzz/fact_manager/fact_manager.h"
+#include "source/fuzz/overflow_id_source.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -26,15 +29,28 @@
 class TransformationContext {
  public:
   // Constructs a transformation context with a given fact manager and validator
-  // options.
-  TransformationContext(FactManager* fact_manager,
+  // options.  Overflow ids are not available from a transformation context
+  // constructed in this way.
+  TransformationContext(std::unique_ptr<FactManager>,
                         spv_validator_options validator_options);
 
+  // Constructs a transformation context with a given fact manager, validator
+  // options and overflow id source.
+  TransformationContext(std::unique_ptr<FactManager>,
+                        spv_validator_options validator_options,
+                        std::unique_ptr<OverflowIdSource> overflow_id_source);
+
   ~TransformationContext();
 
-  FactManager* GetFactManager() { return fact_manager_; }
+  FactManager* GetFactManager() { return fact_manager_.get(); }
 
-  const FactManager* GetFactManager() const { return fact_manager_; }
+  const FactManager* GetFactManager() const { return fact_manager_.get(); }
+
+  OverflowIdSource* GetOverflowIdSource() { return overflow_id_source_.get(); }
+
+  const OverflowIdSource* GetOverflowIdSource() const {
+    return overflow_id_source_.get();
+  }
 
   spv_validator_options GetValidatorOptions() const {
     return validator_options_;
@@ -43,11 +59,13 @@
  private:
   // Manages facts that inform whether transformations can be applied, and that
   // are produced by applying transformations.
-  FactManager* fact_manager_;
+  std::unique_ptr<FactManager> fact_manager_;
 
   // Options to control validation when deciding whether transformations can be
   // applied.
   spv_validator_options validator_options_;
+
+  std::unique_ptr<OverflowIdSource> overflow_id_source_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/transformation_duplicate_region_with_selection.cpp b/source/fuzz/transformation_duplicate_region_with_selection.cpp
new file mode 100644
index 0000000..07758cd
--- /dev/null
+++ b/source/fuzz/transformation_duplicate_region_with_selection.cpp
@@ -0,0 +1,682 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_duplicate_region_with_selection.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationDuplicateRegionWithSelection::
+    TransformationDuplicateRegionWithSelection(
+        const spvtools::fuzz::protobufs::
+            TransformationDuplicateRegionWithSelection& message)
+    : message_(message) {}
+
+TransformationDuplicateRegionWithSelection::
+    TransformationDuplicateRegionWithSelection(
+        uint32_t new_entry_fresh_id, uint32_t condition_id,
+        uint32_t merge_label_fresh_id, uint32_t entry_block_id,
+        uint32_t exit_block_id,
+        const std::map<uint32_t, uint32_t>& original_label_to_duplicate_label,
+        const std::map<uint32_t, uint32_t>& original_id_to_duplicate_id,
+        const std::map<uint32_t, uint32_t>& original_id_to_phi_id) {
+  message_.set_new_entry_fresh_id(new_entry_fresh_id);
+  message_.set_condition_id(condition_id);
+  message_.set_merge_label_fresh_id(merge_label_fresh_id);
+  message_.set_entry_block_id(entry_block_id);
+  message_.set_exit_block_id(exit_block_id);
+  *message_.mutable_original_label_to_duplicate_label() =
+      fuzzerutil::MapToRepeatedUInt32Pair(original_label_to_duplicate_label);
+  *message_.mutable_original_id_to_duplicate_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_duplicate_id);
+  *message_.mutable_original_id_to_phi_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_phi_id);
+}
+
+bool TransformationDuplicateRegionWithSelection::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Instruction with the id |condition_id| must exist and must be of a bool
+  // type.
+  auto bool_instr =
+      ir_context->get_def_use_mgr()->GetDef(message_.condition_id());
+  if (bool_instr == nullptr || !bool_instr->type_id()) {
+    return false;
+  }
+  if (!ir_context->get_type_mgr()->GetType(bool_instr->type_id())->AsBool()) {
+    return false;
+  }
+
+  // The |new_entry_fresh_id| must be fresh and distinct.
+  std::set<uint32_t> ids_used_by_this_transformation;
+  if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+          message_.new_entry_fresh_id(), ir_context,
+          &ids_used_by_this_transformation)) {
+    return false;
+  }
+
+  // The |merge_label_fresh_id| must be fresh and distinct.
+  if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+          message_.merge_label_fresh_id(), ir_context,
+          &ids_used_by_this_transformation)) {
+    return false;
+  }
+
+  // The entry and exit block ids must refer to blocks.
+  for (auto block_id : {message_.entry_block_id(), message_.exit_block_id()}) {
+    auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id);
+    if (!block_label || block_label->opcode() != SpvOpLabel) {
+      return false;
+    }
+  }
+  auto entry_block = ir_context->cfg()->block(message_.entry_block_id());
+  auto exit_block = ir_context->cfg()->block(message_.exit_block_id());
+
+  // The |entry_block| and the |exit_block| must be in the same function.
+  if (entry_block->GetParent() != exit_block->GetParent()) {
+    return false;
+  }
+
+  // The |entry_block| must dominate the |exit_block|.
+  auto dominator_analysis =
+      ir_context->GetDominatorAnalysis(entry_block->GetParent());
+  if (!dominator_analysis->Dominates(entry_block, exit_block)) {
+    return false;
+  }
+
+  // The |exit_block| must post-dominate the |entry_block|.
+  auto postdominator_analysis =
+      ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
+  if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
+    return false;
+  }
+
+  auto enclosing_function = entry_block->GetParent();
+
+  // |entry_block| cannot be the first block of the |enclosing_function|.
+  if (&*enclosing_function->begin() == entry_block) {
+    return false;
+  }
+
+  // To make the process of resolving OpPhi instructions easier, we require that
+  // the entry block has only one predecessor.
+  auto entry_block_preds = ir_context->cfg()->preds(entry_block->id());
+  std::sort(entry_block_preds.begin(), entry_block_preds.end());
+  entry_block_preds.erase(
+      std::unique(entry_block_preds.begin(), entry_block_preds.end()),
+      entry_block_preds.end());
+  if (entry_block_preds.size() > 1) {
+    return false;
+  }
+
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785):
+  //     The following code has been copied from TransformationOutlineFunction.
+  //     Consider refactoring to avoid duplication.
+  auto region_set = GetRegionBlocks(ir_context, entry_block, exit_block);
+
+  // Check whether |region_set| really is a single-entry single-exit region, and
+  // also check whether structured control flow constructs and their merge
+  // and continue constructs are either wholly in or wholly out of the region -
+  // e.g. avoid the situation where the region contains the head of a loop but
+  // not the loop's continue construct.
+  //
+  // This is achieved by going through every block in the |enclosing_function|
+  for (auto& block : *enclosing_function) {
+    if (&block == exit_block) {
+      // It is not OK for the exit block to head a loop construct or a
+      // conditional construct.
+      if (block.GetMergeInst()) {
+        return false;
+      }
+      continue;
+    }
+    if (region_set.count(&block) != 0) {
+      // The block is in the region and is not the region's exit block.  Let's
+      // see whether all of the block's successors are in the region. If they
+      // are not, the region is not single-entry single-exit.
+      bool all_successors_in_region = true;
+      block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
+                                     &region_set](uint32_t successor) -> bool {
+        if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
+          all_successors_in_region = false;
+          return false;
+        }
+        return true;
+      });
+      if (!all_successors_in_region) {
+        return false;
+      }
+    }
+
+    if (auto merge = block.GetMergeInst()) {
+      // The block is a loop or selection header. The header and its
+      // associated merge block must be both in the region or both be
+      // outside the region.
+      auto merge_block =
+          ir_context->cfg()->block(merge->GetSingleWordOperand(0));
+      if (region_set.count(&block) != region_set.count(merge_block)) {
+        return false;
+      }
+    }
+
+    if (auto loop_merge = block.GetLoopMergeInst()) {
+      // The continue target of a loop must be within the region if and only if
+      // the header of the loop is.
+      auto continue_target =
+          ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
+      // The continue target is a single-entry, single-exit region. Therefore,
+      // if the continue target is the exit block, the region might not contain
+      // the loop header. However, we would like to exclude this situation,
+      // since it would be impossible for the modified exit block to branch to
+      // the new selection merge block. In this scenario the exit block is
+      // required to branch to the loop header.
+      if (region_set.count(&block) != region_set.count(continue_target)) {
+        return false;
+      }
+    }
+  }
+
+  // Get the maps from the protobuf.
+  std::map<uint32_t, uint32_t> original_label_to_duplicate_label =
+      fuzzerutil::RepeatedUInt32PairToMap(
+          message_.original_label_to_duplicate_label());
+
+  std::map<uint32_t, uint32_t> original_id_to_duplicate_id =
+      fuzzerutil::RepeatedUInt32PairToMap(
+          message_.original_id_to_duplicate_id());
+
+  std::map<uint32_t, uint32_t> original_id_to_phi_id =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id());
+
+  for (auto block : region_set) {
+    // The label of every block in the region must be present in the map
+    // |original_label_to_duplicate_label|, unless overflow ids are present.
+    if (original_label_to_duplicate_label.count(block->id()) == 0) {
+      if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+        return false;
+      }
+    } else {
+      auto duplicate_label = original_label_to_duplicate_label[block->id()];
+      // Each id assigned to labels in the region must be distinct and fresh.
+      if (!duplicate_label ||
+          !CheckIdIsFreshAndNotUsedByThisTransformation(
+              duplicate_label, ir_context, &ids_used_by_this_transformation)) {
+        return false;
+      }
+    }
+    for (auto instr : *block) {
+      if (!instr.HasResultId()) {
+        continue;
+      }
+      // Every instruction with a result id in the region must be present in the
+      // map |original_id_to_duplicate_id|, unless overflow ids are present.
+      if (original_id_to_duplicate_id.count(instr.result_id()) == 0) {
+        if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+          return false;
+        }
+      } else {
+        auto duplicate_id = original_id_to_duplicate_id[instr.result_id()];
+        // Id assigned to this result id in the region must be distinct and
+        // fresh.
+        if (!duplicate_id ||
+            !CheckIdIsFreshAndNotUsedByThisTransformation(
+                duplicate_id, ir_context, &ids_used_by_this_transformation)) {
+          return false;
+        }
+      }
+      if (&instr == &*exit_block->tail() ||
+          fuzzerutil::IdIsAvailableBeforeInstruction(
+              ir_context, &*exit_block->tail(), instr.result_id())) {
+        // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3787):
+        //     Consider not adding OpPhi instructions for the pointers and
+        //     sampled images which are unused after the region, so that the
+        //     transformation could be still applicable.
+
+        // Using pointers with OpPhi requires capability VariablePointers.
+        if (ir_context->get_def_use_mgr()->GetDef(instr.type_id())->opcode() ==
+                SpvOpTypePointer &&
+            !ir_context->get_feature_mgr()->HasCapability(
+                SpvCapabilityVariablePointers)) {
+          return false;
+        }
+
+        // OpTypeSampledImage cannot be the result type of an OpPhi instruction.
+        if (ir_context->get_def_use_mgr()->GetDef(instr.type_id())->opcode() ==
+            SpvOpTypeSampledImage) {
+          return false;
+        }
+
+        // Every instruction with a result id available at the end of the region
+        // must be present in the map |original_id_to_phi_id|, unless overflow
+        // ids are present.
+        if (original_id_to_phi_id.count(instr.result_id()) == 0) {
+          if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+            return false;
+          }
+        } else {
+          auto phi_id = original_id_to_phi_id[instr.result_id()];
+          // Id assigned to this result id in the region must be distinct and
+          // fresh.
+          if (!phi_id ||
+              !CheckIdIsFreshAndNotUsedByThisTransformation(
+                  phi_id, ir_context, &ids_used_by_this_transformation)) {
+            return false;
+          }
+        }
+      }
+    }
+  }
+  return true;
+}
+
+void TransformationDuplicateRegionWithSelection::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_entry_fresh_id());
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.merge_label_fresh_id());
+
+  // Create the new entry block containing the main conditional instruction. Set
+  // its parent to the parent of the original entry block, since it is located
+  // in the same function.
+  std::unique_ptr<opt::BasicBlock> new_entry_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.new_entry_fresh_id(),
+          opt::Instruction::OperandList()));
+  auto entry_block = ir_context->cfg()->block(message_.entry_block_id());
+  auto enclosing_function = entry_block->GetParent();
+  auto exit_block = ir_context->cfg()->block(message_.exit_block_id());
+
+  // Get the blocks contained in the region.
+  std::set<opt::BasicBlock*> region_blocks =
+      GetRegionBlocks(ir_context, entry_block, exit_block);
+
+  // Construct the merge block.
+  std::unique_ptr<opt::BasicBlock> merge_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(opt::Instruction(
+          ir_context, SpvOpLabel, 0, message_.merge_label_fresh_id(),
+          opt::Instruction::OperandList())));
+
+  // Get the maps from the protobuf.
+  std::map<uint32_t, uint32_t> original_label_to_duplicate_label =
+      fuzzerutil::RepeatedUInt32PairToMap(
+          message_.original_label_to_duplicate_label());
+
+  std::map<uint32_t, uint32_t> original_id_to_duplicate_id =
+      fuzzerutil::RepeatedUInt32PairToMap(
+          message_.original_id_to_duplicate_id());
+
+  std::map<uint32_t, uint32_t> original_id_to_phi_id =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id());
+
+  // Use oveflow ids to fill in any required ids that are missing from these
+  // maps.
+  for (auto block : region_blocks) {
+    if (original_label_to_duplicate_label.count(block->id()) == 0) {
+      original_label_to_duplicate_label.insert(
+          {block->id(),
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+    for (auto instr : *block) {
+      if (!instr.HasResultId()) {
+        continue;
+      }
+      if (original_id_to_duplicate_id.count(instr.result_id()) == 0) {
+        original_id_to_duplicate_id.insert(
+            {instr.result_id(), transformation_context->GetOverflowIdSource()
+                                    ->GetNextOverflowId()});
+      }
+      if (&instr == &*exit_block->tail() ||
+          fuzzerutil::IdIsAvailableBeforeInstruction(
+              ir_context, &*exit_block->tail(), instr.result_id())) {
+        if (original_id_to_phi_id.count(instr.result_id()) == 0) {
+          original_id_to_phi_id.insert(
+              {instr.result_id(), transformation_context->GetOverflowIdSource()
+                                      ->GetNextOverflowId()});
+        }
+      }
+    }
+  }
+
+  // Before adding duplicate blocks, we need to update the OpPhi instructions in
+  // the successors of the |exit_block|. We know that the execution of the
+  // transformed region will end in |merge_block|. Hence, we need to change all
+  // occurrences of the label id of the |exit_block| to the label id of the
+  // |merge_block|.
+  exit_block->ForEachSuccessorLabel([this, ir_context](uint32_t label_id) {
+    auto block = ir_context->cfg()->block(label_id);
+    for (auto& instr : *block) {
+      if (instr.opcode() == SpvOpPhi) {
+        instr.ForEachId([this](uint32_t* id) {
+          if (*id == message_.exit_block_id()) {
+            *id = message_.merge_label_fresh_id();
+          }
+        });
+      }
+    }
+  });
+
+  // Get vector of predecessors id of |entry_block|. Remove any duplicate
+  // values.
+  auto entry_block_preds = ir_context->cfg()->preds(entry_block->id());
+  std::sort(entry_block_preds.begin(), entry_block_preds.end());
+  entry_block_preds.erase(
+      unique(entry_block_preds.begin(), entry_block_preds.end()),
+      entry_block_preds.end());
+  // We know that |entry_block| has only one predecessor, since the region is
+  // single-entry, single-exit and its constructs and their merge blocks must be
+  // either wholly within or wholly outside of the region.
+  assert(entry_block_preds.size() == 1 &&
+         "The entry of the region to be duplicated can have only one "
+         "predecessor.");
+  uint32_t entry_block_pred_id =
+      ir_context->get_instr_block(entry_block_preds[0])->id();
+  // Update all the OpPhi instructions in the |entry_block|. Change every
+  // occurrence of |entry_block_pred_id| to the id of |new_entry|, because we
+  // will insert |new_entry| before |entry_block|.
+  for (auto& instr : *entry_block) {
+    if (instr.opcode() == SpvOpPhi) {
+      instr.ForEachId([this, entry_block_pred_id](uint32_t* id) {
+        if (*id == entry_block_pred_id) {
+          *id = message_.new_entry_fresh_id();
+        }
+      });
+    }
+  }
+
+  // Duplication of blocks will invalidate iterators. Store all the blocks from
+  // the enclosing function.
+  std::vector<opt::BasicBlock*> blocks;
+  for (auto& block : *enclosing_function) {
+    blocks.push_back(&block);
+  }
+
+  opt::BasicBlock* previous_block = nullptr;
+  opt::BasicBlock* duplicated_exit_block = nullptr;
+  // Iterate over all blocks of the function to duplicate blocks of the original
+  // region and their instructions.
+  for (auto& block : blocks) {
+    // The block must be contained in the region.
+    if (region_blocks.count(block) == 0) {
+      continue;
+    }
+
+    fuzzerutil::UpdateModuleIdBound(
+        ir_context, original_label_to_duplicate_label[block->id()]);
+
+    std::unique_ptr<opt::BasicBlock> duplicated_block =
+        MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpLabel, 0,
+            original_label_to_duplicate_label[block->id()],
+            opt::Instruction::OperandList()));
+
+    for (auto& instr : *block) {
+      // Case where an instruction is the terminator of the exit block is
+      // handled separately.
+      if (block == exit_block && instr.IsBlockTerminator()) {
+        switch (instr.opcode()) {
+          case SpvOpBranch:
+          case SpvOpBranchConditional:
+          case SpvOpReturn:
+          case SpvOpReturnValue:
+          case SpvOpUnreachable:
+          case SpvOpKill:
+            continue;
+          default:
+            assert(false &&
+                   "Unexpected terminator for |exit_block| of the region.");
+        }
+      }
+      // Duplicate the instruction.
+      auto cloned_instr = instr.Clone(ir_context);
+      duplicated_block->AddInstruction(
+          std::unique_ptr<opt::Instruction>(cloned_instr));
+
+      fuzzerutil::UpdateModuleIdBound(
+          ir_context, original_id_to_duplicate_id[instr.result_id()]);
+
+      // If an id from the original region was used in this instruction,
+      // replace it with the value from |original_id_to_duplicate_id|.
+      // If a label from the original region was used in this instruction,
+      // replace it with the value from |original_label_to_duplicate_label|.
+      cloned_instr->ForEachId(
+          [original_id_to_duplicate_id,
+           original_label_to_duplicate_label](uint32_t* op) {
+            if (original_id_to_duplicate_id.count(*op) != 0) {
+              *op = original_id_to_duplicate_id.at(*op);
+            }
+            if (original_label_to_duplicate_label.count(*op) != 0) {
+              *op = original_label_to_duplicate_label.at(*op);
+            }
+          });
+    }
+
+    // If the block is the first duplicated block, insert it after the exit
+    // block of the original region. Otherwise, insert it after the preceding
+    // one.
+    auto duplicated_block_ptr = duplicated_block.get();
+    if (previous_block) {
+      enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block),
+                                                previous_block);
+    } else {
+      enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block),
+                                                exit_block);
+    }
+    previous_block = duplicated_block_ptr;
+    if (block == exit_block) {
+      // After execution of the loop, this variable stores a pointer to the last
+      // duplicated block.
+      duplicated_exit_block = duplicated_block_ptr;
+    }
+  }
+
+  for (auto& block : region_blocks) {
+    for (auto& instr : *block) {
+      if (instr.result_id() != 0 &&
+          (&instr == &*exit_block->tail() ||
+           fuzzerutil::IdIsAvailableBeforeInstruction(
+               ir_context, &*exit_block->tail(), instr.result_id()))) {
+        // Add the OpPhi instruction for every result id that is
+        // available at the end of the region (the last instruction
+        // of the |exit_block|)
+        merge_block->AddInstruction(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpPhi, instr.type_id(),
+            original_id_to_phi_id[instr.result_id()],
+            opt::Instruction::OperandList({
+                {SPV_OPERAND_TYPE_ID, {instr.result_id()}},
+                {SPV_OPERAND_TYPE_ID, {exit_block->id()}},
+                {SPV_OPERAND_TYPE_ID,
+                 {original_id_to_duplicate_id[instr.result_id()]}},
+                {SPV_OPERAND_TYPE_ID, {duplicated_exit_block->id()}},
+            })));
+
+        fuzzerutil::UpdateModuleIdBound(
+            ir_context, original_id_to_phi_id[instr.result_id()]);
+
+        // If the instruction has been remapped by an OpPhi, look
+        // for all its uses outside of the region and outside of the
+        // merge block (to not overwrite just added instructions in
+        // the merge block) and replace the original instruction id
+        // with the id of the corresponding OpPhi instruction.
+        ir_context->get_def_use_mgr()->ForEachUse(
+            &instr,
+            [ir_context, &instr, region_blocks, original_id_to_phi_id,
+             &merge_block](opt::Instruction* user, uint32_t operand_index) {
+              auto user_block = ir_context->get_instr_block(user);
+              if ((region_blocks.find(user_block) != region_blocks.end()) ||
+                  user_block == merge_block.get()) {
+                return;
+              }
+              user->SetOperand(operand_index,
+                               {original_id_to_phi_id.at(instr.result_id())});
+            });
+      }
+    }
+  }
+
+  // Construct a conditional instruction in the |new_entry_block|.
+  // If the condition is true, the execution proceeds in the
+  // |entry_block| of the original region. If the condition is
+  // false, the execution proceeds in the first block of the
+  // duplicated region.
+  new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpSelectionMerge, 0, 0,
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}},
+           {SPV_OPERAND_TYPE_SELECTION_CONTROL,
+            {SpvSelectionControlMaskNone}}})));
+
+  new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranchConditional, 0, 0,
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID, {message_.condition_id()}},
+           {SPV_OPERAND_TYPE_ID, {message_.entry_block_id()}},
+           {SPV_OPERAND_TYPE_ID,
+            {original_label_to_duplicate_label[message_.entry_block_id()]}}})));
+
+  // Move the terminator of |exit_block| to the end of
+  // |merge_block|.
+  auto exit_block_terminator = exit_block->terminator();
+  auto cloned_instr = exit_block_terminator->Clone(ir_context);
+  merge_block->AddInstruction(std::unique_ptr<opt::Instruction>(cloned_instr));
+  ir_context->KillInst(exit_block_terminator);
+
+  // Add OpBranch instruction to the merge block at the end of
+  // |exit_block| and at the end of |duplicated_exit_block|, so that
+  // the execution proceeds in the |merge_block|.
+  opt::Instruction merge_branch_instr = opt::Instruction(
+      ir_context, SpvOpBranch, 0, 0,
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}}}));
+  exit_block->AddInstruction(MakeUnique<opt::Instruction>(merge_branch_instr));
+  duplicated_exit_block->AddInstruction(
+      std::unique_ptr<opt::Instruction>(merge_branch_instr.Clone(ir_context)));
+
+  // Execution needs to start in the |new_entry_block|. Change all
+  // the uses of |entry_block_label_instr| outside of the original
+  // region to |message_.new_entry_fresh_id|.
+  auto entry_block_label_instr =
+      ir_context->get_def_use_mgr()->GetDef(message_.entry_block_id());
+  ir_context->get_def_use_mgr()->ForEachUse(
+      entry_block_label_instr,
+      [this, ir_context, region_blocks](opt::Instruction* user,
+                                        uint32_t operand_index) {
+        auto user_block = ir_context->get_instr_block(user);
+        if ((region_blocks.count(user_block) != 0)) {
+          return;
+        }
+        switch (user->opcode()) {
+          case SpvOpSwitch:
+          case SpvOpBranch:
+          case SpvOpBranchConditional:
+          case SpvOpLoopMerge:
+          case SpvOpSelectionMerge: {
+            user->SetOperand(operand_index, {message_.new_entry_fresh_id()});
+          } break;
+          case SpvOpName:
+            break;
+          default:
+            assert(false &&
+                   "The label id cannot be used by instructions "
+                   "other than "
+                   "OpSwitch, OpBranch, OpBranchConditional, "
+                   "OpLoopMerge, "
+                   "OpSelectionMerge");
+        }
+      });
+
+  opt::Instruction* merge_block_terminator = merge_block->terminator();
+  switch (merge_block_terminator->opcode()) {
+    case SpvOpReturnValue:
+    case SpvOpBranchConditional: {
+      uint32_t operand = merge_block_terminator->GetSingleWordInOperand(0);
+      if (original_id_to_phi_id.count(operand)) {
+        merge_block_terminator->SetInOperand(
+            0, {original_id_to_phi_id.at(operand)});
+      }
+      break;
+    }
+    default:
+      break;
+  }
+
+  // Insert the merge block after the |duplicated_exit_block| (the
+  // last duplicated block).
+  enclosing_function->InsertBasicBlockAfter(std::move(merge_block),
+                                            duplicated_exit_block);
+
+  // Insert the |new_entry_block| before the entry block of the
+  // original region.
+  enclosing_function->InsertBasicBlockBefore(std::move(new_entry_block),
+                                             entry_block);
+
+  // Since we have changed the module, most of the analysis are now
+  // invalid. We can invalidate analyses now after all of the blocks
+  // have been registered.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785):
+//     The following method has been copied from
+//     TransformationOutlineFunction. Consider refactoring to avoid
+//     duplication.
+std::set<opt::BasicBlock*>
+TransformationDuplicateRegionWithSelection::GetRegionBlocks(
+    opt::IRContext* ir_context, opt::BasicBlock* entry_block,
+    opt::BasicBlock* exit_block) {
+  auto enclosing_function = entry_block->GetParent();
+  auto dominator_analysis =
+      ir_context->GetDominatorAnalysis(enclosing_function);
+  auto postdominator_analysis =
+      ir_context->GetPostDominatorAnalysis(enclosing_function);
+
+  // A block belongs to a region between the entry block and the exit
+  // block if and only if it is dominated by the entry block and
+  // post-dominated by the exit block.
+  std::set<opt::BasicBlock*> result;
+  for (auto& block : *enclosing_function) {
+    if (dominator_analysis->Dominates(entry_block, &block) &&
+        postdominator_analysis->Dominates(exit_block, &block)) {
+      result.insert(&block);
+    }
+  }
+  return result;
+}
+
+protobufs::Transformation
+TransformationDuplicateRegionWithSelection::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_duplicate_region_with_selection() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t>
+TransformationDuplicateRegionWithSelection::GetFreshIds() const {
+  std::unordered_set<uint32_t> result = {message_.new_entry_fresh_id(),
+                                         message_.merge_label_fresh_id()};
+  for (auto& pair : message_.original_label_to_duplicate_label()) {
+    result.insert(pair.second());
+  }
+  for (auto& pair : message_.original_id_to_duplicate_id()) {
+    result.insert(pair.second());
+  }
+  for (auto& pair : message_.original_id_to_phi_id()) {
+    result.insert(pair.second());
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_duplicate_region_with_selection.h b/source/fuzz/transformation_duplicate_region_with_selection.h
new file mode 100644
index 0000000..d6f0ad9
--- /dev/null
+++ b/source/fuzz/transformation_duplicate_region_with_selection.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_DUPLICATE_REGION_WITH_SELECTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_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 TransformationDuplicateRegionWithSelection : public Transformation {
+ public:
+  explicit TransformationDuplicateRegionWithSelection(
+      const protobufs::TransformationDuplicateRegionWithSelection& message);
+
+  explicit TransformationDuplicateRegionWithSelection(
+      uint32_t new_entry_fresh_id, uint32_t condition_id,
+      uint32_t merge_label_fresh_id, uint32_t entry_block_id,
+      uint32_t exit_block_id,
+      const std::map<uint32_t, uint32_t>& original_label_to_duplicate_label,
+      const std::map<uint32_t, uint32_t>& original_id_to_duplicate_id,
+      const std::map<uint32_t, uint32_t>& original_id_to_phi_id);
+
+  // - |new_entry_fresh_id|, |merge_label_fresh_id| must be fresh and distinct.
+  // - |condition_id| must refer to a valid instruction of boolean type.
+  // - |entry_block_id| and |exit_block_id| must refer to valid blocks and they
+  //   must form a single-entry, single-exit region. Its constructs and their
+  //   merge blocks must be either wholly within or wholly outside of the
+  //   region.
+  // - |original_label_to_duplicate_label| must contain at least a key for every
+  //   block in the original region.
+  // - |original_id_to_duplicate_id| must contain at least a key for every
+  //   result id in the original region.
+  // - |original_id_to_phi_id| must contain at least a key for every result id
+  //   available at the end of the original region.
+  // - In each of these three maps, each value must be a distinct, fresh id.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // A transformation that inserts a conditional statement with a boolean
+  // expression of arbitrary value and duplicates a given single-entry,
+  // single-exit region, so that it is present in each conditional branch and
+  // will be executed regardless of which branch will be taken.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  // Returns the set of blocks dominated by |entry_block| and post-dominated
+  // by |exit_block|.
+  static std::set<opt::BasicBlock*> GetRegionBlocks(
+      opt::IRContext* ir_context, opt::BasicBlock* entry_block,
+      opt::BasicBlock* exit_block);
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationDuplicateRegionWithSelection message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_H_
diff --git a/source/fuzz/transformation_equation_instruction.cpp b/source/fuzz/transformation_equation_instruction.cpp
index e27cd29..32e8351 100644
--- a/source/fuzz/transformation_equation_instruction.cpp
+++ b/source/fuzz/transformation_equation_instruction.cpp
@@ -50,8 +50,8 @@
   if (!insert_before) {
     return false;
   }
-  // The input ids must all exist, not be OpUndef, and be available before this
-  // instruction.
+  // The input ids must all exist, not be OpUndef, not be irrelevant, and be
+  // available before this instruction.
   for (auto id : message_.in_operand_id()) {
     auto inst = ir_context->get_def_use_mgr()->GetDef(id);
     if (!inst) {
@@ -92,9 +92,13 @@
 
   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 
-  transformation_context->GetFactManager()->AddFactIdEquation(
-      message_.fresh_id(), static_cast<SpvOp>(message_.opcode()), rhs_id,
-      ir_context);
+  // Add an equation fact as long as the result id is not irrelevant (it could
+  // be if we are inserting into a dead block).
+  if (!transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.fresh_id())) {
+    transformation_context->GetFactManager()->AddFactIdEquation(
+        message_.fresh_id(), static_cast<SpvOp>(message_.opcode()), rhs_id);
+  }
 }
 
 protobufs::Transformation TransformationEquationInstruction::ToMessage() const {
@@ -283,5 +287,10 @@
   }
 }
 
+std::unordered_set<uint32_t> TransformationEquationInstruction::GetFreshIds()
+    const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_equation_instruction.h b/source/fuzz/transformation_equation_instruction.h
index 9ed01a8..4afc85f 100644
--- a/source/fuzz/transformation_equation_instruction.h
+++ b/source/fuzz/transformation_equation_instruction.h
@@ -60,6 +60,8 @@
   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:
diff --git a/source/fuzz/transformation_expand_vector_reduction.cpp b/source/fuzz/transformation_expand_vector_reduction.cpp
new file mode 100644
index 0000000..640aea2
--- /dev/null
+++ b/source/fuzz/transformation_expand_vector_reduction.cpp
@@ -0,0 +1,170 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_expand_vector_reduction.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationExpandVectorReduction::TransformationExpandVectorReduction(
+    const spvtools::fuzz::protobufs::TransformationExpandVectorReduction&
+        message)
+    : message_(message) {}
+
+TransformationExpandVectorReduction::TransformationExpandVectorReduction(
+    const uint32_t instruction_result_id,
+    const std::vector<uint32_t>& fresh_ids) {
+  message_.set_instruction_result_id(instruction_result_id);
+  *message_.mutable_fresh_ids() =
+      google::protobuf::RepeatedField<google::protobuf::uint32>(
+          fresh_ids.begin(), fresh_ids.end());
+}
+
+bool TransformationExpandVectorReduction::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  auto* instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
+
+  // |instruction| must be defined.
+  if (!instruction) {
+    return false;
+  }
+
+  // |instruction| must be OpAny or OpAll.
+  if (instruction->opcode() != SpvOpAny && instruction->opcode() != SpvOpAll) {
+    return false;
+  }
+
+  // |message_.fresh_ids.size| must have the exact number of fresh ids required
+  // to apply the transformation.
+  if (static_cast<uint32_t>(message_.fresh_ids().size()) !=
+      GetRequiredFreshIdCount(ir_context, instruction)) {
+    return false;
+  }
+
+  std::set<uint32_t> ids_used_by_this_transformation;
+  for (uint32_t fresh_id : message_.fresh_ids()) {
+    // All ids in |message_.fresh_ids| must be fresh.
+    if (!fuzzerutil::IsFreshId(ir_context, fresh_id)) {
+      return false;
+    }
+
+    // All fresh ids need to be distinct.
+    if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+            fresh_id, ir_context, &ids_used_by_this_transformation)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationExpandVectorReduction::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto* instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
+  auto* vector = ir_context->get_def_use_mgr()->GetDef(
+      instruction->GetSingleWordInOperand(0));
+  uint32_t vector_component_count = ir_context->get_type_mgr()
+                                        ->GetType(vector->type_id())
+                                        ->AsVector()
+                                        ->element_count();
+
+  // Fresh id iterator.
+  auto fresh_id = message_.fresh_ids().begin();
+
+  // |vector_components| are the ids of the extracted components from |vector|.
+  std::vector<uint32_t> vector_components;
+
+  for (uint32_t i = 0; i < vector_component_count; i++) {
+    // Extracts the i-th |vector| component.
+    auto vector_component = opt::Instruction(
+        ir_context, SpvOpCompositeExtract, instruction->type_id(), *fresh_id++,
+        {{SPV_OPERAND_TYPE_ID, {vector->result_id()}},
+         {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}});
+    instruction->InsertBefore(MakeUnique<opt::Instruction>(vector_component));
+    fuzzerutil::UpdateModuleIdBound(ir_context, vector_component.result_id());
+    vector_components.push_back(vector_component.result_id());
+  }
+
+  // The first two |vector| components are used in the first logical operation.
+  auto logical_instruction = opt::Instruction(
+      ir_context,
+      instruction->opcode() == SpvOpAny ? SpvOpLogicalOr : SpvOpLogicalAnd,
+      instruction->type_id(), *fresh_id++,
+      {{SPV_OPERAND_TYPE_ID, {vector_components[0]}},
+       {SPV_OPERAND_TYPE_ID, {vector_components[1]}}});
+  instruction->InsertBefore(MakeUnique<opt::Instruction>(logical_instruction));
+  fuzzerutil::UpdateModuleIdBound(ir_context, logical_instruction.result_id());
+
+  // Evaluates the remaining components.
+  for (uint32_t i = 2; i < vector_components.size(); i++) {
+    logical_instruction = opt::Instruction(
+        ir_context, logical_instruction.opcode(), instruction->type_id(),
+        *fresh_id++,
+        {{SPV_OPERAND_TYPE_ID, {vector_components[i]}},
+         {SPV_OPERAND_TYPE_ID, {logical_instruction.result_id()}}});
+    instruction->InsertBefore(
+        MakeUnique<opt::Instruction>(logical_instruction));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    logical_instruction.result_id());
+  }
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // 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)) {
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(logical_instruction.result_id(), {}),
+        MakeDataDescriptor(instruction->result_id(), {}));
+  }
+}
+
+protobufs::Transformation TransformationExpandVectorReduction::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_expand_vector_reduction() = message_;
+  return result;
+}
+
+uint32_t TransformationExpandVectorReduction::GetRequiredFreshIdCount(
+    opt::IRContext* ir_context, opt::Instruction* instruction) {
+  // For each vector component, 1 OpCompositeExtract and 1 OpLogical* (except
+  // for the first component) instructions will be inserted.
+  return 2 * ir_context->get_type_mgr()
+                 ->GetType(ir_context->get_def_use_mgr()
+                               ->GetDef(instruction->GetSingleWordInOperand(0))
+                               ->type_id())
+                 ->AsVector()
+                 ->element_count() -
+         1;
+}
+
+std::unordered_set<uint32_t> TransformationExpandVectorReduction::GetFreshIds()
+    const {
+  std::unordered_set<uint32_t> result;
+  for (auto id : message_.fresh_ids()) {
+    result.insert(id);
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_expand_vector_reduction.h b/source/fuzz/transformation_expand_vector_reduction.h
new file mode 100644
index 0000000..e4cc953
--- /dev/null
+++ b/source/fuzz/transformation_expand_vector_reduction.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_EXPAND_VECTOR_REDUCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_EXPAND_VECTOR_REDUCTION_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 {
+
+// clang-format off
+// SPIR-V code to help understand the transformation.
+//
+// -------------------------------------------------------------------------------
+// |           Reference shader           |            Variant shader            |
+// -------------------------------------------------------------------------------
+// |       OpCapability Shader            |       OpCapability Shader            |
+// |  %1 = OpExtInstImport "GLSL.std.450" |  %1 = OpExtInstImport "GLSL.std.450" |
+// |       OpMemoryModel Logical GLSL450  |       OpMemoryModel Logical GLSL450  |
+// |       OpEntryPoint Vertex %9 "main"  |       OpEntryPoint Vertex %9 "main"  |
+// |                                      |                                      |
+// | ; Types                              | ; Types                              |
+// |  %2 = OpTypeBool                     |  %2 = OpTypeBool                     |
+// |  %3 = OpTypeVector %2 2              |  %3 = OpTypeVector %2 2              |
+// |  %4 = OpTypeVoid                     |  %4 = OpTypeVoid                     |
+// |  %5 = OpTypeFunction %4              |  %5 = OpTypeFunction %4              |
+// |                                      |                                      |
+// | ; Constants                          | ; Constants                          |
+// |  %6 = OpConstantTrue %2              |  %6 = OpConstantTrue %2              |
+// |  %7 = OpConstantFalse %2             |  %7 = OpConstantFalse %2             |
+// |  %8 = OpConstantComposite %3 %6 %7   |  %8 = OpConstantComposite %3 %6 %7   |
+// |                                      |                                      |
+// | ; main function                      | ; main function                      |
+// |  %9 = OpFunction %4 None %5          |  %9 = OpFunction %4 None %5          |
+// | %10 = OpLabel                        | %10 = OpLabel                        |
+// | %11 = OpAny %2 %8                    |                                      |
+// | %12 = OpAll %2 %8                    | ; Add OpAny synonym                  |
+// |       OpReturn                       | %13 = OpCompositeExtract %2 %8 0     |
+// |       OpFunctionEnd                  | %14 = OpCompositeExtract %2 %8 1     |
+// |                                      | %15 = OpLogicalOr %2 %13 %14         |
+// |                                      | %11 = OpAny %2 %8                    |
+// |                                      |                                      |
+// |                                      | ; Add OpAll synonym                  |
+// |                                      | %16 = OpCompositeExtract %2 %8 0     |
+// |                                      | %17 = OpCompositeExtract %2 %8 1     |
+// |                                      | %18 = OpLogicalAnd %2 %16 %17        |
+// |                                      | %12 = OpAll %2 %8                    |
+// |                                      |       OpReturn                       |
+// |                                      |       OpFunctionEnd                  |
+// -------------------------------------------------------------------------------
+//
+// %11 and %15 are synonymous
+// %12 and %18 are synonymous
+// clang-format on
+class TransformationExpandVectorReduction : public Transformation {
+ public:
+  explicit TransformationExpandVectorReduction(
+      const protobufs::TransformationExpandVectorReduction& message);
+
+  TransformationExpandVectorReduction(const uint32_t instruction_result_id,
+                                      const std::vector<uint32_t>& fresh_ids);
+
+  // - |message_.instruction_result_id| must be OpAny or OpAll.
+  // - |message_.fresh_ids| must be fresh ids needed to apply the
+  //   transformation.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds synonyms for OpAny and OpAll instructions by evaluating each vector
+  // component with the corresponding logical operation.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns the number of fresh ids required to apply the transformation.
+  static uint32_t GetRequiredFreshIdCount(opt::IRContext* ir_context,
+                                          opt::Instruction* instruction);
+
+ private:
+  protobufs::TransformationExpandVectorReduction message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_EXPAND_VECTOR_REDUCTION_H_
diff --git a/source/fuzz/transformation_flatten_conditional_branch.cpp b/source/fuzz/transformation_flatten_conditional_branch.cpp
new file mode 100644
index 0000000..bad4972
--- /dev/null
+++ b/source/fuzz/transformation_flatten_conditional_branch.cpp
@@ -0,0 +1,991 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_flatten_conditional_branch.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
+    const protobufs::TransformationFlattenConditionalBranch& message)
+    : message_(message) {}
+
+TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
+    uint32_t header_block_id, bool true_branch_first,
+    uint32_t fresh_id_for_bvec2_selector, uint32_t fresh_id_for_bvec3_selector,
+    uint32_t fresh_id_for_bvec4_selector,
+    const std::vector<protobufs::SideEffectWrapperInfo>&
+        side_effect_wrappers_info) {
+  message_.set_header_block_id(header_block_id);
+  message_.set_true_branch_first(true_branch_first);
+  message_.set_fresh_id_for_bvec2_selector(fresh_id_for_bvec2_selector);
+  message_.set_fresh_id_for_bvec3_selector(fresh_id_for_bvec3_selector);
+  message_.set_fresh_id_for_bvec4_selector(fresh_id_for_bvec4_selector);
+  for (auto const& side_effect_wrapper_info : side_effect_wrappers_info) {
+    *message_.add_side_effect_wrapper_info() = side_effect_wrapper_info;
+  }
+}
+
+bool TransformationFlattenConditionalBranch::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  auto header_block =
+      fuzzerutil::MaybeFindBlock(ir_context, message_.header_block_id());
+
+  // The block must have been found and it must be a selection header.
+  if (!header_block || !header_block->GetMergeInst() ||
+      header_block->GetMergeInst()->opcode() != SpvOpSelectionMerge) {
+    return false;
+  }
+
+  // The header block must end with an OpBranchConditional instruction.
+  if (header_block->terminator()->opcode() != SpvOpBranchConditional) {
+    return false;
+  }
+
+  // The branch condition cannot be irrelevant: we will make reference to it
+  // multiple times and we need to be guaranteed that these references will
+  // yield the same result; if they are replaced by other ids that will not
+  // work.
+  if (transformation_context.GetFactManager()->IdIsIrrelevant(
+          header_block->terminator()->GetSingleWordInOperand(0))) {
+    return false;
+  }
+
+  std::set<uint32_t> used_fresh_ids;
+
+  // If ids have been provided to be used as vector guards for OpSelect
+  // instructions then they must be fresh.
+  for (uint32_t fresh_id_for_bvec_selector :
+       {message_.fresh_id_for_bvec2_selector(),
+        message_.fresh_id_for_bvec3_selector(),
+        message_.fresh_id_for_bvec4_selector()}) {
+    if (fresh_id_for_bvec_selector != 0) {
+      if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+              fresh_id_for_bvec_selector, ir_context, &used_fresh_ids)) {
+        return false;
+      }
+    }
+  }
+
+  if (OpSelectArgumentsAreRestricted(ir_context)) {
+    // OpPhi instructions at the convergence block for the selection are handled
+    // by turning them into OpSelect instructions.  As the SPIR-V version in use
+    // has restrictions on the arguments that OpSelect can take, we must check
+    // that any OpPhi instructions are compatible with these restrictions.
+    uint32_t convergence_block_id =
+        FindConvergenceBlock(ir_context, *header_block);
+    // Consider every OpPhi instruction at the convergence block.
+    if (!ir_context->cfg()
+             ->block(convergence_block_id)
+             ->WhileEachPhiInst([this,
+                                 ir_context](opt::Instruction* inst) -> bool {
+               // Decide whether the OpPhi can be handled based on its result
+               // type.
+               opt::Instruction* phi_result_type =
+                   ir_context->get_def_use_mgr()->GetDef(inst->type_id());
+               switch (phi_result_type->opcode()) {
+                 case SpvOpTypeBool:
+                 case SpvOpTypeInt:
+                 case SpvOpTypeFloat:
+                 case SpvOpTypePointer:
+                   // Fine: OpSelect can work directly on scalar and pointer
+                   // types.
+                   return true;
+                 case SpvOpTypeVector: {
+                   // In its restricted form, OpSelect can only select between
+                   // vectors if the condition of the select is a boolean
+                   // boolean vector.  We thus require the appropriate boolean
+                   // vector type to be present.
+                   uint32_t bool_type_id =
+                       fuzzerutil::MaybeGetBoolType(ir_context);
+                   uint32_t dimension =
+                       phi_result_type->GetSingleWordInOperand(1);
+                   if (fuzzerutil::MaybeGetVectorType(ir_context, bool_type_id,
+                                                      dimension) == 0) {
+                     // The required boolean vector type is not present.
+                     return false;
+                   }
+                   // The transformation needs to be equipped with a fresh id
+                   // in which to store the vectorized version of the selection
+                   // construct's condition.
+                   switch (dimension) {
+                     case 2:
+                       return message_.fresh_id_for_bvec2_selector() != 0;
+                     case 3:
+                       return message_.fresh_id_for_bvec3_selector() != 0;
+                     default:
+                       assert(dimension == 4 && "Invalid vector dimension.");
+                       return message_.fresh_id_for_bvec4_selector() != 0;
+                   }
+                 }
+                 default:
+                   return false;
+               }
+             })) {
+      return false;
+    }
+  }
+
+  // Use a set to keep track of the instructions that require fresh ids.
+  std::set<opt::Instruction*> instructions_that_need_ids;
+
+  // Check that, if there are enough ids, the conditional can be flattened and,
+  // if so, add all the problematic instructions that need to be enclosed inside
+  // conditionals to |instructions_that_need_ids|.
+  if (!GetProblematicInstructionsIfConditionalCanBeFlattened(
+          ir_context, header_block, &instructions_that_need_ids)) {
+    return false;
+  }
+
+  // Get the mapping from instructions to the fresh ids needed to enclose them
+  // inside conditionals.
+  auto insts_to_wrapper_info = GetInstructionsToWrapperInfo(ir_context);
+
+  {
+    // Check the ids in the map.
+    for (const auto& inst_to_info : insts_to_wrapper_info) {
+      // Check the fresh ids needed for all of the instructions that need to be
+      // enclosed inside a conditional.
+      for (uint32_t id : {inst_to_info.second.merge_block_id(),
+                          inst_to_info.second.execute_block_id()}) {
+        if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(
+                       id, ir_context, &used_fresh_ids)) {
+          return false;
+        }
+      }
+
+      // Check the other ids needed, if the instruction needs a placeholder.
+      if (InstructionNeedsPlaceholder(ir_context, *inst_to_info.first)) {
+        // Check the fresh ids.
+        for (uint32_t id : {inst_to_info.second.actual_result_id(),
+                            inst_to_info.second.alternative_block_id(),
+                            inst_to_info.second.placeholder_result_id()}) {
+          if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(
+                         id, ir_context, &used_fresh_ids)) {
+            return false;
+          }
+        }
+
+        // Check that the placeholder value id exists, has the right type and is
+        // available to use at this point.
+        auto value_def = ir_context->get_def_use_mgr()->GetDef(
+            inst_to_info.second.value_to_copy_id());
+        if (!value_def ||
+            value_def->type_id() != inst_to_info.first->type_id() ||
+            !fuzzerutil::IdIsAvailableBeforeInstruction(
+                ir_context, inst_to_info.first,
+                inst_to_info.second.value_to_copy_id())) {
+          return false;
+        }
+      }
+    }
+  }
+
+  // If some instructions that require ids are not in the map, the
+  // transformation needs overflow ids to be applicable.
+  for (auto instruction : instructions_that_need_ids) {
+    if (insts_to_wrapper_info.count(instruction) == 0 &&
+        !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+      return false;
+    }
+  }
+
+  // All checks were passed.
+  return true;
+}
+
+void TransformationFlattenConditionalBranch::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // branch = 1 corresponds to the true branch, branch = 2 corresponds to the
+  // false branch. If the true branch is to be laid out first, we need to visit
+  // the false branch first, because each branch is moved to right after the
+  // header while it is visited.
+  std::vector<uint32_t> branches = {2, 1};
+  if (!message_.true_branch_first()) {
+    // Similarly, we need to visit the true branch first, if we want it to be
+    // laid out after the false branch.
+    branches = {1, 2};
+  }
+
+  auto header_block = ir_context->cfg()->block(message_.header_block_id());
+
+  // Get the ids of the starting blocks of the first and last branches to be
+  // laid out. The first branch is the true branch iff
+  // |message_.true_branch_first| is true.
+  auto branch_instruction = header_block->terminator();
+  uint32_t first_block_first_branch_id =
+      branch_instruction->GetSingleWordInOperand(branches[1]);
+  uint32_t first_block_last_branch_id =
+      branch_instruction->GetSingleWordInOperand(branches[0]);
+
+  uint32_t convergence_block_id =
+      FindConvergenceBlock(ir_context, *header_block);
+
+  // If the OpBranchConditional instruction in the header branches to the same
+  // block for both values of the condition, this is the convergence block (the
+  // flow does not actually diverge) and the OpPhi instructions in it are still
+  // valid, so we do not need to make any changes.
+  if (first_block_first_branch_id != first_block_last_branch_id) {
+    RewriteOpPhiInstructionsAtConvergenceBlock(
+        *header_block, convergence_block_id, ir_context);
+  }
+
+  // Get the mapping from instructions to fresh ids.
+  auto insts_to_info = GetInstructionsToWrapperInfo(ir_context);
+
+  // Get a reference to the last block in the first branch that will be laid out
+  // (this depends on |message_.true_branch_first|). The last block is the block
+  // in the branch just before flow converges (it might not exist).
+  opt::BasicBlock* last_block_first_branch = nullptr;
+
+  // Keep track of blocks and ids for which we should later add dead block and
+  // irrelevant id facts.  We wait until we have finished applying the
+  // transformation before adding these facts, so that the fact manager has
+  // access to the fully up-to-date module.
+  std::vector<uint32_t> dead_blocks;
+  std::vector<uint32_t> irrelevant_ids;
+
+  // Adjust the conditional branches by enclosing problematic instructions
+  // within conditionals and get references to the last block in each branch.
+  for (uint32_t branch : branches) {
+    auto current_block = header_block;
+    // Get the id of the first block in this branch.
+    uint32_t next_block_id = branch_instruction->GetSingleWordInOperand(branch);
+
+    // Consider all blocks in the branch until the convergence block is reached.
+    while (next_block_id != convergence_block_id) {
+      // Move the next block to right after the current one.
+      current_block->GetParent()->MoveBasicBlockToAfter(next_block_id,
+                                                        current_block);
+
+      // Move forward in the branch.
+      current_block = ir_context->cfg()->block(next_block_id);
+
+      // Find all the instructions in the current block which need to be
+      // enclosed inside conditionals.
+      std::vector<opt::Instruction*> problematic_instructions;
+
+      current_block->ForEachInst(
+          [&problematic_instructions](opt::Instruction* instruction) {
+            if (instruction->opcode() != SpvOpLabel &&
+                instruction->opcode() != SpvOpBranch &&
+                !fuzzerutil::InstructionHasNoSideEffects(*instruction)) {
+              problematic_instructions.push_back(instruction);
+            }
+          });
+
+      uint32_t condition_id =
+          header_block->terminator()->GetSingleWordInOperand(0);
+
+      // Enclose all of the problematic instructions in conditionals, with the
+      // same condition as the selection construct being flattened.
+      for (auto instruction : problematic_instructions) {
+        // Get the info needed by this instruction to wrap it inside a
+        // conditional.
+        protobufs::SideEffectWrapperInfo wrapper_info;
+
+        if (insts_to_info.count(instruction) != 0) {
+          // Get the fresh ids from the map, if present.
+          wrapper_info = insts_to_info[instruction];
+        } else {
+          // If we could not get it from the map, use overflow ids. We don't
+          // need to set |wrapper_info.instruction|, as it will not be used.
+          wrapper_info.set_merge_block_id(
+              transformation_context->GetOverflowIdSource()
+                  ->GetNextOverflowId());
+          wrapper_info.set_execute_block_id(
+              transformation_context->GetOverflowIdSource()
+                  ->GetNextOverflowId());
+
+          if (InstructionNeedsPlaceholder(ir_context, *instruction)) {
+            // Ge the fresh ids from the overflow ids.
+            wrapper_info.set_actual_result_id(
+                transformation_context->GetOverflowIdSource()
+                    ->GetNextOverflowId());
+            wrapper_info.set_alternative_block_id(
+                transformation_context->GetOverflowIdSource()
+                    ->GetNextOverflowId());
+            wrapper_info.set_placeholder_result_id(
+                transformation_context->GetOverflowIdSource()
+                    ->GetNextOverflowId());
+
+            // Try to find a zero constant. It does not matter whether it is
+            // relevant or irrelevant.
+            for (bool is_irrelevant : {true, false}) {
+              wrapper_info.set_value_to_copy_id(
+                  fuzzerutil::MaybeGetZeroConstant(
+                      ir_context, *transformation_context,
+                      instruction->type_id(), is_irrelevant));
+              if (wrapper_info.value_to_copy_id()) {
+                break;
+              }
+            }
+          }
+        }
+
+        // Enclose the instruction in a conditional and get the merge block
+        // generated by this operation (this is where all the following
+        // instructions will be).
+        current_block = EncloseInstructionInConditional(
+            ir_context, *transformation_context, current_block, instruction,
+            wrapper_info, condition_id, branch == 1, &dead_blocks,
+            &irrelevant_ids);
+      }
+
+      next_block_id = current_block->terminator()->GetSingleWordInOperand(0);
+
+      // If the next block is the convergence block and this the branch that
+      // will be laid out right after the header, record this as the last block
+      // in the first branch.
+      if (next_block_id == convergence_block_id && branch == branches[1]) {
+        last_block_first_branch = current_block;
+      }
+    }
+  }
+
+  // The current header should unconditionally branch to the starting block in
+  // the first branch to be laid out, if such a branch exists (i.e. the header
+  // does not branch directly to the convergence block), and to the starting
+  // block in the last branch to be laid out otherwise.
+  uint32_t after_header = first_block_first_branch_id != convergence_block_id
+                              ? first_block_first_branch_id
+                              : first_block_last_branch_id;
+
+  // Kill the merge instruction and the branch instruction in the current
+  // header.
+  auto merge_inst = header_block->GetMergeInst();
+  ir_context->KillInst(branch_instruction);
+  ir_context->KillInst(merge_inst);
+
+  // Add a new, unconditional, branch instruction from the current header to
+  // |after_header|.
+  header_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranch, 0, 0,
+      opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {after_header}}}));
+
+  // If the first branch to be laid out exists, change the branch instruction so
+  // that the last block in such branch unconditionally branches to the first
+  // block in the other branch (or the convergence block if there is no other
+  // branch) and change the OpPhi instructions in the last branch accordingly
+  // (the predecessor changed).
+  if (last_block_first_branch) {
+    last_block_first_branch->terminator()->SetInOperand(
+        0, {first_block_last_branch_id});
+
+    // Change the OpPhi instructions of the last branch (if there is another
+    // branch) so that the predecessor is now the last block of the first
+    // branch. The block must have a single predecessor, so the operand
+    // specifying the predecessor is always in the same position.
+    if (first_block_last_branch_id != convergence_block_id) {
+      ir_context->get_instr_block(first_block_last_branch_id)
+          ->ForEachPhiInst(
+              [&last_block_first_branch](opt::Instruction* phi_inst) {
+                // The operand specifying the predecessor is the second input
+                // operand.
+                phi_inst->SetInOperand(1, {last_block_first_branch->id()});
+              });
+    }
+  }
+
+  // Invalidate all analyses
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Now that we have finished adding blocks and ids to the module and
+  // invalidated existing analyses, update the fact manager regarding dead
+  // blocks and irrelevant ids.
+  for (auto dead_block : dead_blocks) {
+    transformation_context->GetFactManager()->AddFactBlockIsDead(dead_block);
+  }
+  for (auto irrelevant_id : irrelevant_ids) {
+    transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
+        irrelevant_id);
+  }
+}
+
+protobufs::Transformation TransformationFlattenConditionalBranch::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_flatten_conditional_branch() = message_;
+  return result;
+}
+
+bool TransformationFlattenConditionalBranch::
+    GetProblematicInstructionsIfConditionalCanBeFlattened(
+        opt::IRContext* ir_context, opt::BasicBlock* header,
+        std::set<opt::Instruction*>* instructions_that_need_ids) {
+  uint32_t merge_block_id = header->MergeBlockIdIfAny();
+  assert(merge_block_id &&
+         header->GetMergeInst()->opcode() == SpvOpSelectionMerge &&
+         header->terminator()->opcode() == SpvOpBranchConditional &&
+         "|header| must be the header of a conditional.");
+
+  auto enclosing_function = header->GetParent();
+  auto dominator_analysis =
+      ir_context->GetDominatorAnalysis(enclosing_function);
+  auto postdominator_analysis =
+      ir_context->GetPostDominatorAnalysis(enclosing_function);
+
+  // Check that the header and the merge block describe a single-entry,
+  // single-exit region.
+  if (!dominator_analysis->Dominates(header->id(), merge_block_id) ||
+      !postdominator_analysis->Dominates(merge_block_id, header->id())) {
+    return false;
+  }
+
+  // Traverse the CFG starting from the header and check that, for all the
+  // blocks that can be reached by the header before the flow converges:
+  //  - they don't contain merge, barrier or OpSampledImage instructions
+  //  - they branch unconditionally to another block
+  //  Add any side-effecting instruction, requiring fresh ids, to
+  //  |instructions_that_need_ids|
+  std::list<uint32_t> to_check;
+  header->ForEachSuccessorLabel(
+      [&to_check](uint32_t label) { to_check.push_back(label); });
+
+  while (!to_check.empty()) {
+    uint32_t block_id = to_check.front();
+    to_check.pop_front();
+
+    // If the block post-dominates the header, this is where flow converges, and
+    // we don't need to check this branch any further, because the
+    // transformation will only change the part of the graph where flow is
+    // divergent.
+    if (postdominator_analysis->Dominates(block_id, header->id())) {
+      continue;
+    }
+
+    auto block = ir_context->cfg()->block(block_id);
+
+    // The block must not have a merge instruction, because inner constructs are
+    // not allowed.
+    if (block->GetMergeInst()) {
+      return false;
+    }
+
+    // The terminator instruction for the block must be OpBranch.
+    if (block->terminator()->opcode() != SpvOpBranch) {
+      return false;
+    }
+
+    // Check all of the instructions in the block.
+    bool all_instructions_compatible =
+        block->WhileEachInst([ir_context, instructions_that_need_ids](
+                                 opt::Instruction* instruction) {
+          // We can ignore OpLabel instructions.
+          if (instruction->opcode() == SpvOpLabel) {
+            return true;
+          }
+
+          // If the instruction is a branch, it must be an unconditional branch.
+          if (instruction->IsBranch()) {
+            return instruction->opcode() == SpvOpBranch;
+          }
+
+          // We cannot go ahead if we encounter an instruction that cannot be
+          // handled.
+          if (!InstructionCanBeHandled(ir_context, *instruction)) {
+            return false;
+          }
+
+          // If the instruction has side effects, add it to the
+          // |instructions_that_need_ids| set.
+          if (!fuzzerutil::InstructionHasNoSideEffects(*instruction)) {
+            instructions_that_need_ids->emplace(instruction);
+          }
+
+          return true;
+        });
+
+    if (!all_instructions_compatible) {
+      return false;
+    }
+
+    // Add the successor of this block to the list of blocks that need to be
+    // checked.
+    to_check.push_back(block->terminator()->GetSingleWordInOperand(0));
+  }
+
+  // All the blocks are compatible with the transformation and this is indeed a
+  // single-entry, single-exit region.
+  return true;
+}
+
+bool TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder(
+    opt::IRContext* ir_context, const opt::Instruction& instruction) {
+  assert(!fuzzerutil::InstructionHasNoSideEffects(instruction) &&
+         InstructionCanBeHandled(ir_context, instruction) &&
+         "The instruction must have side effects and it must be possible to "
+         "enclose it inside a conditional.");
+
+  if (instruction.HasResultId()) {
+    // We need a placeholder iff the type is not Void.
+    auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
+    return type && !type->AsVoid();
+  }
+
+  return false;
+}
+
+std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo>
+TransformationFlattenConditionalBranch::GetInstructionsToWrapperInfo(
+    opt::IRContext* ir_context) const {
+  std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo>
+      instructions_to_ids;
+  for (const auto& wrapper_info : message_.side_effect_wrapper_info()) {
+    auto instruction = FindInstruction(wrapper_info.instruction(), ir_context);
+    if (instruction) {
+      instructions_to_ids.emplace(instruction, wrapper_info);
+    }
+  }
+
+  return instructions_to_ids;
+}
+
+opt::BasicBlock*
+TransformationFlattenConditionalBranch::EncloseInstructionInConditional(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context, opt::BasicBlock* block,
+    opt::Instruction* instruction,
+    const protobufs::SideEffectWrapperInfo& wrapper_info, uint32_t condition_id,
+    bool exec_if_cond_true, std::vector<uint32_t>* dead_blocks,
+    std::vector<uint32_t>* irrelevant_ids) const {
+  // Get the next instruction (it will be useful for splitting).
+  auto next_instruction = instruction->NextNode();
+
+  // Update the module id bound.
+  for (uint32_t id :
+       {wrapper_info.merge_block_id(), wrapper_info.execute_block_id()}) {
+    fuzzerutil::UpdateModuleIdBound(ir_context, id);
+  }
+
+  // Create the block where the instruction is executed by splitting the
+  // original block.
+  auto execute_block = block->SplitBasicBlock(
+      ir_context, wrapper_info.execute_block_id(),
+      fuzzerutil::GetIteratorForInstruction(block, instruction));
+
+  // Create the merge block for the conditional that we are about to create by
+  // splitting execute_block (this will leave |instruction| as the only
+  // instruction in |execute_block|).
+  auto merge_block = execute_block->SplitBasicBlock(
+      ir_context, wrapper_info.merge_block_id(),
+      fuzzerutil::GetIteratorForInstruction(execute_block, next_instruction));
+
+  // Propagate the fact that the block is dead to the newly-created blocks.
+  if (transformation_context.GetFactManager()->BlockIsDead(block->id())) {
+    dead_blocks->emplace_back(execute_block->id());
+    dead_blocks->emplace_back(merge_block->id());
+  }
+
+  // Initially, consider the merge block as the alternative block to branch to
+  // if the instruction should not be executed.
+  auto alternative_block = merge_block;
+
+  // Add an unconditional branch from |execute_block| to |merge_block|.
+  execute_block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranch, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {merge_block->id()}}}));
+
+  // If the instruction requires a placeholder, it means that it has a result id
+  // and its result needs to be able to be used later on, and we need to:
+  // - add an additional block |ids.alternative_block_id| where a placeholder
+  //   result id (using fresh id |ids.placeholder_result_id|) is obtained either
+  //   by using OpCopyObject and copying |ids.value_to_copy_id| or, if such id
+  //   was not given and a suitable constant was not found, by using OpUndef.
+  // - mark |ids.placeholder_result_id| as irrelevant
+  // - change the result id of the instruction to a fresh id
+  //   (|ids.actual_result_id|).
+  // - add an OpPhi instruction, which will have the original result id of the
+  //   instruction, in the merge block.
+  if (InstructionNeedsPlaceholder(ir_context, *instruction)) {
+    // Update the module id bound with the additional ids.
+    for (uint32_t id :
+         {wrapper_info.actual_result_id(), wrapper_info.alternative_block_id(),
+          wrapper_info.placeholder_result_id()}) {
+      fuzzerutil::UpdateModuleIdBound(ir_context, id);
+    }
+
+    // Create a new block using |fresh_ids.alternative_block_id| for its label.
+    auto alternative_block_temp =
+        MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpLabel, 0, wrapper_info.alternative_block_id(),
+            opt::Instruction::OperandList{}));
+
+    // Keep the original result id of the instruction in a variable.
+    uint32_t original_result_id = instruction->result_id();
+
+    // Set the result id of the instruction to be |ids.actual_result_id|.
+    instruction->SetResultId(wrapper_info.actual_result_id());
+
+    // Add a placeholder instruction, with the same type as the original
+    // instruction and id |ids.placeholder_result_id|, to the new block.
+    if (wrapper_info.value_to_copy_id()) {
+      // If there is an available id to copy from, the placeholder instruction
+      // will be %placeholder_result_id = OpCopyObject %type %value_to_copy_id
+      alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpCopyObject, instruction->type_id(),
+          wrapper_info.placeholder_result_id(),
+          opt::Instruction::OperandList{
+              {SPV_OPERAND_TYPE_ID, {wrapper_info.value_to_copy_id()}}}));
+    } else {
+      // If there is no such id, use an OpUndef instruction.
+      alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpUndef, instruction->type_id(),
+          wrapper_info.placeholder_result_id(),
+          opt::Instruction::OperandList{}));
+    }
+
+    // Mark |ids.placeholder_result_id| as irrelevant.
+    irrelevant_ids->emplace_back(wrapper_info.placeholder_result_id());
+
+    // Add an unconditional branch from the new block to the merge block.
+    alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpBranch, 0, 0,
+        opt::Instruction::OperandList{
+            {SPV_OPERAND_TYPE_ID, {merge_block->id()}}}));
+
+    // Insert the block before the merge block.
+    alternative_block = block->GetParent()->InsertBasicBlockBefore(
+        std::move(alternative_block_temp), merge_block);
+
+    // Using the original instruction result id, add an OpPhi instruction to the
+    // merge block, which will either take the value of the result of the
+    // instruction or the placeholder value defined in the alternative block.
+    merge_block->begin().InsertBefore(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpPhi, instruction->type_id(), original_result_id,
+        opt::Instruction::OperandList{
+            {SPV_OPERAND_TYPE_ID, {instruction->result_id()}},
+            {SPV_OPERAND_TYPE_ID, {execute_block->id()}},
+            {SPV_OPERAND_TYPE_ID, {wrapper_info.placeholder_result_id()}},
+            {SPV_OPERAND_TYPE_ID, {alternative_block->id()}}}));
+
+    // Propagate the fact that the block is dead to the new block.
+    if (transformation_context.GetFactManager()->BlockIsDead(block->id())) {
+      dead_blocks->emplace_back(alternative_block->id());
+    }
+  }
+
+  // Depending on whether the instruction should be executed in the if branch or
+  // in the else branch, get the corresponding ids.
+  auto if_block_id = (exec_if_cond_true ? execute_block : alternative_block)
+                         ->GetLabel()
+                         ->result_id();
+  auto else_block_id = (exec_if_cond_true ? alternative_block : execute_block)
+                           ->GetLabel()
+                           ->result_id();
+
+  // Add an OpSelectionMerge instruction to the block.
+  block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpSelectionMerge, 0, 0,
+      opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {merge_block->id()}},
+                                    {SPV_OPERAND_TYPE_SELECTION_CONTROL,
+                                     {SpvSelectionControlMaskNone}}}));
+
+  // Add an OpBranchConditional, to the block, using |condition_id| as the
+  // condition and branching to |if_block_id| if the condition is true and to
+  // |else_block_id| if the condition is false.
+  block->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranchConditional, 0, 0,
+      opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {condition_id}},
+                                    {SPV_OPERAND_TYPE_ID, {if_block_id}},
+                                    {SPV_OPERAND_TYPE_ID, {else_block_id}}}));
+
+  return merge_block;
+}
+
+bool TransformationFlattenConditionalBranch::InstructionCanBeHandled(
+    opt::IRContext* ir_context, const opt::Instruction& instruction) {
+  // We can handle all instructions with no side effects.
+  if (fuzzerutil::InstructionHasNoSideEffects(instruction)) {
+    return true;
+  }
+
+  // We cannot handle barrier instructions, while we should be able to handle
+  // all other instructions by enclosing them inside a conditional.
+  if (instruction.opcode() == SpvOpControlBarrier ||
+      instruction.opcode() == SpvOpMemoryBarrier ||
+      instruction.opcode() == SpvOpNamedBarrierInitialize ||
+      instruction.opcode() == SpvOpMemoryNamedBarrier ||
+      instruction.opcode() == SpvOpTypeNamedBarrier) {
+    return false;
+  }
+
+  // We cannot handle OpSampledImage instructions, as they need to be in the
+  // same block as their use.
+  if (instruction.opcode() == SpvOpSampledImage) {
+    return false;
+  }
+
+  // We cannot handle a sampled image load, because we re-work loads using
+  // conditional branches and OpPhi instructions, and the result type of OpPhi
+  // cannot be OpTypeSampledImage.
+  if (instruction.opcode() == SpvOpLoad &&
+      ir_context->get_def_use_mgr()->GetDef(instruction.type_id())->opcode() ==
+          SpvOpTypeSampledImage) {
+    return false;
+  }
+
+  // We cannot handle instructions with an id which return a void type, if the
+  // result id is used in the module (e.g. a function call to a function that
+  // returns nothing).
+  if (instruction.HasResultId()) {
+    auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
+    assert(type && "The type should be found in the module");
+
+    if (type->AsVoid() &&
+        !ir_context->get_def_use_mgr()->WhileEachUse(
+            instruction.result_id(),
+            [](opt::Instruction* use_inst, uint32_t use_index) {
+              // Return false if the id is used as an input operand.
+              return use_index <
+                     use_inst->NumOperands() - use_inst->NumInOperands();
+            })) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+std::unordered_set<uint32_t>
+TransformationFlattenConditionalBranch::GetFreshIds() const {
+  std::unordered_set<uint32_t> result = {
+      message_.fresh_id_for_bvec2_selector(),
+      message_.fresh_id_for_bvec3_selector(),
+      message_.fresh_id_for_bvec4_selector()};
+  for (auto& side_effect_wrapper_info : message_.side_effect_wrapper_info()) {
+    result.insert(side_effect_wrapper_info.merge_block_id());
+    result.insert(side_effect_wrapper_info.execute_block_id());
+    result.insert(side_effect_wrapper_info.actual_result_id());
+    result.insert(side_effect_wrapper_info.alternative_block_id());
+    result.insert(side_effect_wrapper_info.placeholder_result_id());
+  }
+  return result;
+}
+
+uint32_t TransformationFlattenConditionalBranch::FindConvergenceBlock(
+    opt::IRContext* ir_context, const opt::BasicBlock& header_block) {
+  uint32_t result = header_block.terminator()->GetSingleWordInOperand(1);
+  auto postdominator_analysis =
+      ir_context->GetPostDominatorAnalysis(header_block.GetParent());
+  while (!postdominator_analysis->Dominates(result, header_block.id())) {
+    auto current_block = ir_context->get_instr_block(result);
+    // If the transformation is applicable, the terminator is OpBranch.
+    result = current_block->terminator()->GetSingleWordInOperand(0);
+  }
+  return result;
+}
+
+bool TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
+    opt::IRContext* ir_context) {
+  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: {
+      return true;
+    }
+    default:
+      return false;
+  }
+}
+
+void TransformationFlattenConditionalBranch::AddBooleanVectorConstructorToBlock(
+    uint32_t fresh_id, uint32_t dimension,
+    const opt::Operand& branch_condition_operand, opt::IRContext* ir_context,
+    opt::BasicBlock* block) const {
+  opt::Instruction::OperandList in_operands;
+  for (uint32_t i = 0; i < dimension; i++) {
+    in_operands.emplace_back(branch_condition_operand);
+  }
+  block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpCompositeConstruct,
+      fuzzerutil::MaybeGetVectorType(
+          ir_context, fuzzerutil::MaybeGetBoolType(ir_context), dimension),
+      fresh_id, in_operands));
+  fuzzerutil::UpdateModuleIdBound(ir_context, fresh_id);
+}
+
+void TransformationFlattenConditionalBranch::
+    RewriteOpPhiInstructionsAtConvergenceBlock(
+        const opt::BasicBlock& header_block, uint32_t convergence_block_id,
+        opt::IRContext* ir_context) const {
+  const opt::Instruction& branch_instruction = *header_block.terminator();
+
+  const opt::Operand& branch_condition_operand =
+      branch_instruction.GetInOperand(0);
+
+  // If we encounter OpPhi instructions on vector types then we may need to
+  // introduce vector versions of the selection construct's condition to use
+  // in corresponding OpSelect instructions.  These booleans track whether we
+  // need to introduce such boolean vectors.
+  bool require_2d_boolean_vector = false;
+  bool require_3d_boolean_vector = false;
+  bool require_4d_boolean_vector = false;
+
+  // Consider every OpPhi instruction at the convergence block.
+  opt::BasicBlock* convergence_block =
+      ir_context->get_instr_block(convergence_block_id);
+  convergence_block->ForEachPhiInst(
+      [this, &branch_condition_operand, branch_instruction,
+       convergence_block_id, &header_block, ir_context,
+       &require_2d_boolean_vector, &require_3d_boolean_vector,
+       &require_4d_boolean_vector](opt::Instruction* phi_inst) {
+        assert(phi_inst->NumInOperands() == 4 &&
+               "We are going to replace an OpPhi with an OpSelect.  This "
+               "only makes sense if the block has two distinct "
+               "predecessors.");
+        // We are going to replace the OpPhi with an OpSelect.  By default,
+        // the condition for the OpSelect will be the branch condition's
+        // operand.  However, if the OpPhi has vector result type we may need
+        // to use a boolean vector as the condition instead.
+        opt::Operand selector_operand = branch_condition_operand;
+        opt::Instruction* type_inst =
+            ir_context->get_def_use_mgr()->GetDef(phi_inst->type_id());
+        if (type_inst->opcode() == SpvOpTypeVector) {
+          uint32_t dimension = type_inst->GetSingleWordInOperand(1);
+          switch (dimension) {
+            case 2:
+              // The OpPhi's result type is a 2D vector.  If a fresh id for a
+              // bvec2 selector was provided then we should use it as the
+              // OpSelect's condition, and note the fact that we will need to
+              // add an instruction to bring this bvec2 into existence.
+              if (message_.fresh_id_for_bvec2_selector() != 0) {
+                selector_operand = {SPV_OPERAND_TYPE_ID,
+                                    {message_.fresh_id_for_bvec2_selector()}};
+                require_2d_boolean_vector = true;
+              }
+              break;
+            case 3:
+              // Similar to the 2D case.
+              if (message_.fresh_id_for_bvec3_selector() != 0) {
+                selector_operand = {SPV_OPERAND_TYPE_ID,
+                                    {message_.fresh_id_for_bvec3_selector()}};
+                require_3d_boolean_vector = true;
+              }
+              break;
+            case 4:
+              // Similar to the 2D case.
+              if (message_.fresh_id_for_bvec4_selector() != 0) {
+                selector_operand = {SPV_OPERAND_TYPE_ID,
+                                    {message_.fresh_id_for_bvec4_selector()}};
+                require_4d_boolean_vector = true;
+              }
+              break;
+            default:
+              assert(dimension == 4 && "Invalid vector dimension.");
+              break;
+          }
+        }
+        std::vector<opt::Operand> operands;
+        operands.emplace_back(selector_operand);
+
+        uint32_t branch_instruction_true_block_id =
+            branch_instruction.GetSingleWordInOperand(1);
+        uint32_t branch_instruction_false_block_id =
+            branch_instruction.GetSingleWordInOperand(2);
+
+        // The OpPhi takes values from two distinct predecessors.  One
+        // predecessor is associated with the "true" path of the conditional
+        // we are flattening, the other with the "false" path, but these
+        // predecessors can appear in either order as operands to the OpPhi
+        // instruction.  We determine in which order the OpPhi inputs should
+        // appear as OpSelect arguments by first checking whether the
+        // convergence block is a direct successor of the selection header, and
+        // otherwise checking dominance of the true and false immediate
+        // successors of the header block.
+        if (branch_instruction_true_block_id == convergence_block_id) {
+          // The branch instruction's true block is the convergence block.  This
+          // means that the OpPhi's value associated with the branch
+          // instruction's block should the "true" result of the OpSelect.
+          assert(branch_instruction_false_block_id != convergence_block_id &&
+                 "Control should not reach here if both branches target the "
+                 "convergence block.");
+          if (phi_inst->GetSingleWordInOperand(1) ==
+              message_.header_block_id()) {
+            operands.emplace_back(phi_inst->GetInOperand(0));
+            operands.emplace_back(phi_inst->GetInOperand(2));
+          } else {
+            assert(phi_inst->GetSingleWordInOperand(3) ==
+                       message_.header_block_id() &&
+                   "Since the convergence block has the header block as one of "
+                   "two predecessors, if it is not handled by the first pair "
+                   "of operands of this OpPhi instruction it should be handled "
+                   "by the second pair.");
+            operands.emplace_back(phi_inst->GetInOperand(2));
+            operands.emplace_back(phi_inst->GetInOperand(0));
+          }
+        } else if (branch_instruction_false_block_id == convergence_block_id) {
+          // The branch instruction's false block is the convergence block. This
+          // means that the OpPhi's value associated with the branch
+          // instruction's block should the "false" result of the OpSelect.
+          if (phi_inst->GetSingleWordInOperand(1) ==
+              message_.header_block_id()) {
+            operands.emplace_back(phi_inst->GetInOperand(2));
+            operands.emplace_back(phi_inst->GetInOperand(0));
+          } else {
+            assert(phi_inst->GetSingleWordInOperand(3) ==
+                       message_.header_block_id() &&
+                   "Since the convergence block has the header block as one of "
+                   "two predecessors, if it is not handled by the first pair "
+                   "of operands of this OpPhi instruction it should be handled "
+                   "by the second pair.");
+            operands.emplace_back(phi_inst->GetInOperand(0));
+            operands.emplace_back(phi_inst->GetInOperand(2));
+          }
+        } else if (ir_context->GetDominatorAnalysis(header_block.GetParent())
+                       ->Dominates(branch_instruction_true_block_id,
+                                   phi_inst->GetSingleWordInOperand(1))) {
+          // The "true" branch  of the conditional is handled first in the
+          // OpPhi's operands; we thus provide operands to OpSelect in the same
+          // order that they appear in the OpPhi.
+          operands.emplace_back(phi_inst->GetInOperand(0));
+          operands.emplace_back(phi_inst->GetInOperand(2));
+        } else {
+          // The "false" branch of the conditional is handled first in the
+          // OpPhi's operands; we thus provide operands to OpSelect in reverse
+          // of the order that they appear in the OpPhi.
+          operands.emplace_back(phi_inst->GetInOperand(2));
+          operands.emplace_back(phi_inst->GetInOperand(0));
+        }
+        phi_inst->SetOpcode(SpvOpSelect);
+        phi_inst->SetInOperands(std::move(operands));
+      });
+
+  // Add boolean vector instructions to the start of the block as required.
+  if (require_2d_boolean_vector) {
+    AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec2_selector(),
+                                       2, branch_condition_operand, ir_context,
+                                       convergence_block);
+  }
+  if (require_3d_boolean_vector) {
+    AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec3_selector(),
+                                       3, branch_condition_operand, ir_context,
+                                       convergence_block);
+  }
+  if (require_4d_boolean_vector) {
+    AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec4_selector(),
+                                       4, branch_condition_operand, ir_context,
+                                       convergence_block);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_flatten_conditional_branch.h b/source/fuzz/transformation_flatten_conditional_branch.h
new file mode 100644
index 0000000..2d5e8d7
--- /dev/null
+++ b/source/fuzz/transformation_flatten_conditional_branch.h
@@ -0,0 +1,156 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_FLATTEN_CONDITIONAL_BRANCH_H_
+#define SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationFlattenConditionalBranch : public Transformation {
+ public:
+  explicit TransformationFlattenConditionalBranch(
+      const protobufs::TransformationFlattenConditionalBranch& message);
+
+  TransformationFlattenConditionalBranch(
+      uint32_t header_block_id, bool true_branch_first,
+      uint32_t fresh_id_for_bvec2_selector,
+      uint32_t fresh_id_for_bvec3_selector,
+      uint32_t fresh_id_for_bvec4_selector,
+      const std::vector<protobufs::SideEffectWrapperInfo>&
+          side_effect_wrappers_info);
+
+  // - |message_.header_block_id| must be the label id of a reachable selection
+  //   header, which ends with an OpBranchConditional instruction.
+  // - The condition of the OpBranchConditional instruction must not be an
+  //   irrelevant id.
+  // - The header block and the merge block must describe a single-entry,
+  //   single-exit region.
+  // - The region must not contain barrier or OpSampledImage instructions.
+  // - The region must not contain selection or loop constructs.
+  // - For each instruction that requires additional fresh ids, then:
+  //   - if the instruction is mapped to the required ids for enclosing it by
+  //     |message_.side_effect_wrapper_info|, these must be valid (the
+  //     fresh ids must be non-zero, fresh and distinct);
+  //   - if there is no such mapping, the transformation context must have
+  //     overflow ids available.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Flattens the selection construct with header |message_.header_block_id|,
+  // changing any OpPhi in the block where the flow converges to OpSelect and
+  // enclosing any instruction with side effects in conditionals so that
+  // they are only executed when they should.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if the conditional headed by |header| can be flattened,
+  // according to the conditions of the IsApplicable method, assuming that
+  // enough fresh ids would be provided. In this case, it fills the
+  // |instructions_that_need_ids| set with all the instructions that would
+  // require fresh ids.
+  // Returns false otherwise.
+  // Assumes that |header| is the header of a conditional, so its last two
+  // instructions are OpSelectionMerge and OpBranchConditional.
+  static bool GetProblematicInstructionsIfConditionalCanBeFlattened(
+      opt::IRContext* ir_context, opt::BasicBlock* header,
+      std::set<opt::Instruction*>* instructions_that_need_ids);
+
+  // Returns true iff the given instruction needs a placeholder to be enclosed
+  // inside a conditional. So, it returns:
+  // - true if the instruction has a non-void result id,
+  // - false if the instruction does not have a result id or has a void result
+  //   id.
+  // Assumes that the instruction has side effects, requiring it to be enclosed
+  // inside a conditional, and that it can be enclosed inside a conditional
+  // keeping the module valid. Assumes that, if the instruction has a void
+  // result type, its result id is not used in the module.
+  static bool InstructionNeedsPlaceholder(opt::IRContext* ir_context,
+                                          const opt::Instruction& instruction);
+
+  // Returns true if and only if the SPIR-V version is such that the arguments
+  // to OpSelect are restricted to only scalars, pointers (if the appropriate
+  // capability is enabled) and component-wise vectors.
+  static bool OpSelectArgumentsAreRestricted(opt::IRContext* ir_context);
+
+  // Find the first block where flow converges (it is not necessarily the merge
+  // block) by walking the true branch until reaching a block that post-
+  // dominates the header.
+  // This is necessary because a potential common set of blocks at the end of
+  // the construct should not be duplicated.
+  static uint32_t FindConvergenceBlock(opt::IRContext* ir_context,
+                                       const opt::BasicBlock& header_block);
+
+ private:
+  // Returns an unordered_map mapping instructions to the info required to
+  // enclose them inside a conditional. It maps the instructions to the
+  // corresponding entry in |message_.side_effect_wrapper_info|.
+  std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo>
+  GetInstructionsToWrapperInfo(opt::IRContext* ir_context) const;
+
+  // Splits the given block, adding a new selection construct so that the given
+  // instruction is only executed if the boolean value of |condition_id| matches
+  // the value of |exec_if_cond_true|.
+  // Assumes that all parameters are consistent.
+  // 2 fresh ids are required if the instruction does not have a result id (the
+  // first two ids in |wrapper_info| must be valid fresh ids), 5 otherwise.
+  // Returns the merge block created.
+  //
+  // |dead_blocks| and |irrelevant_ids| are used to record the ids of blocks
+  // and instructions for which dead block and irrelevant id facts should
+  // ultimately be created.
+  opt::BasicBlock* EncloseInstructionInConditional(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context,
+      opt::BasicBlock* block, opt::Instruction* instruction,
+      const protobufs::SideEffectWrapperInfo& wrapper_info,
+      uint32_t condition_id, bool exec_if_cond_true,
+      std::vector<uint32_t>* dead_blocks,
+      std::vector<uint32_t>* irrelevant_ids) const;
+
+  // Turns every OpPhi instruction of |convergence_block| -- the convergence
+  // block for |header_block| (both in |ir_context|) into an OpSelect
+  // instruction.
+  void RewriteOpPhiInstructionsAtConvergenceBlock(
+      const opt::BasicBlock& header_block, uint32_t convergence_block_id,
+      opt::IRContext* ir_context) const;
+
+  // Adds an OpCompositeExtract instruction to the start of |block| in
+  // |ir_context|, with result id given by |fresh_id|.  The instruction will
+  // make a |dimension|-dimensional boolean vector with
+  // |branch_condition_operand| at every component.
+  void AddBooleanVectorConstructorToBlock(
+      uint32_t fresh_id, uint32_t dimension,
+      const opt::Operand& branch_condition_operand, opt::IRContext* ir_context,
+      opt::BasicBlock* block) const;
+
+  // Returns true if the given instruction either has no side effects or it can
+  // be handled by being enclosed in a conditional.
+  static bool InstructionCanBeHandled(opt::IRContext* ir_context,
+                                      const opt::Instruction& instruction);
+
+  protobufs::TransformationFlattenConditionalBranch message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_FLATTEN_CONDITIONAL_BRANCH_H_
diff --git a/source/fuzz/transformation_function_call.cpp b/source/fuzz/transformation_function_call.cpp
index 432634d..ec95c32 100644
--- a/source/fuzz/transformation_function_call.cpp
+++ b/source/fuzz/transformation_function_call.cpp
@@ -185,5 +185,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationFunctionCall::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_function_call.h b/source/fuzz/transformation_function_call.h
index 4ad7db1..e220d83 100644
--- a/source/fuzz/transformation_function_call.h
+++ b/source/fuzz/transformation_function_call.h
@@ -55,6 +55,8 @@
   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:
diff --git a/source/fuzz/transformation_inline_function.cpp b/source/fuzz/transformation_inline_function.cpp
new file mode 100644
index 0000000..f58b123
--- /dev/null
+++ b/source/fuzz/transformation_inline_function.cpp
@@ -0,0 +1,365 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_inline_function.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationInlineFunction::TransformationInlineFunction(
+    const spvtools::fuzz::protobufs::TransformationInlineFunction& message)
+    : message_(message) {}
+
+TransformationInlineFunction::TransformationInlineFunction(
+    uint32_t function_call_id,
+    const std::map<uint32_t, uint32_t>& result_id_map) {
+  message_.set_function_call_id(function_call_id);
+  *message_.mutable_result_id_map() =
+      fuzzerutil::MapToRepeatedUInt32Pair(result_id_map);
+}
+
+bool TransformationInlineFunction::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // The values in the |message_.result_id_map| must be all fresh and all
+  // distinct.
+  const auto result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+  std::set<uint32_t> ids_used_by_this_transformation;
+  for (auto& pair : result_id_map) {
+    if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+            pair.second, ir_context, &ids_used_by_this_transformation)) {
+      return false;
+    }
+  }
+
+  // |function_call_instruction| must be suitable for inlining.
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  if (!IsSuitableForInlining(ir_context, function_call_instruction)) {
+    return false;
+  }
+
+  // |function_call_instruction| must be the penultimate instruction in its
+  // block and its block termination instruction must be an OpBranch. This
+  // avoids the case where the penultimate instruction is an OpLoopMerge, which
+  // would make the back-edge block not branch to the loop header.
+  auto* function_call_instruction_block =
+      ir_context->get_instr_block(function_call_instruction);
+  if (function_call_instruction !=
+          &*--function_call_instruction_block->tail() ||
+      function_call_instruction_block->terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  for (auto& block : *called_function) {
+    // Since the entry block label will not be inlined, only the remaining
+    // labels must have a corresponding value in the map.
+    if (&block != &*called_function->entry() &&
+        !result_id_map.count(block.id()) &&
+        !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+      return false;
+    }
+
+    // |result_id_map| must have an entry for every result id in the called
+    // function.
+    for (auto& instruction : block) {
+      // If |instruction| has result id, then it must have a mapped id in
+      // |result_id_map|.
+      if (instruction.HasResultId() &&
+          !result_id_map.count(instruction.result_id()) &&
+          !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+        return false;
+      }
+    }
+  }
+
+  // |result_id_map| must not contain an entry for any parameter of the function
+  // that is being inlined.
+  bool found_entry_for_parameter = false;
+  called_function->ForEachParam(
+      [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) {
+        if (result_id_map.count(param->result_id())) {
+          found_entry_for_parameter = true;
+        }
+      });
+  return !found_entry_for_parameter;
+}
+
+void TransformationInlineFunction::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  auto* caller_function =
+      ir_context->get_instr_block(function_call_instruction)->GetParent();
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  std::map<uint32_t, uint32_t> result_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
+
+  // If there are gaps in the result id map, fill them using overflow ids.
+  for (auto& block : *called_function) {
+    if (&block != &*called_function->entry() &&
+        !result_id_map.count(block.id())) {
+      result_id_map.insert(
+          {block.id(),
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+    for (auto& instruction : block) {
+      // If |instruction| has result id, then it must have a mapped id in
+      // |result_id_map|.
+      if (instruction.HasResultId() &&
+          !result_id_map.count(instruction.result_id())) {
+        result_id_map.insert({instruction.result_id(),
+                              transformation_context->GetOverflowIdSource()
+                                  ->GetNextOverflowId()});
+      }
+    }
+  }
+
+  auto* successor_block = ir_context->cfg()->block(
+      ir_context->get_instr_block(function_call_instruction)
+          ->terminator()
+          ->GetSingleWordInOperand(0));
+
+  // Inline the |called_function| entry block.
+  for (auto& entry_block_instruction : *called_function->entry()) {
+    opt::Instruction* inlined_instruction;
+
+    if (entry_block_instruction.opcode() == SpvOpVariable) {
+      // All OpVariable instructions in a function must be in the first block
+      // in the function.
+      inlined_instruction = caller_function->begin()->begin()->InsertBefore(
+          std::unique_ptr<opt::Instruction>(
+              entry_block_instruction.Clone(ir_context)));
+    } else {
+      inlined_instruction = function_call_instruction->InsertBefore(
+          std::unique_ptr<opt::Instruction>(
+              entry_block_instruction.Clone(ir_context)));
+    }
+
+    AdaptInlinedInstruction(result_id_map, ir_context, inlined_instruction);
+  }
+
+  // If the function call's successor block contains OpPhi instructions that
+  // refer to the block containing the call then these will need to be rewritten
+  // to instead refer to the block associated with "returning" from the inlined
+  // function, as this block will be the predecessor of what used to be the
+  // function call's successor block.  We look out for this block.
+  uint32_t new_return_block_id = 0;
+
+  // Inline the |called_function| non-entry blocks.
+  for (auto& block : *called_function) {
+    if (&block == &*called_function->entry()) {
+      continue;
+    }
+
+    // Check whether this is the function's return block.  Take note if it is,
+    // so that OpPhi instructions in the successor of the original function call
+    // block can be re-written.
+    if (block.terminator()->IsReturn()) {
+      assert(new_return_block_id == 0 &&
+             "There should be only one return block.");
+      new_return_block_id = result_id_map.at(block.id());
+    }
+
+    auto* cloned_block = block.Clone(ir_context);
+    cloned_block = caller_function->InsertBasicBlockBefore(
+        std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block);
+    cloned_block->SetParent(caller_function);
+    cloned_block->GetLabel()->SetResultId(result_id_map.at(cloned_block->id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context, cloned_block->id());
+
+    for (auto& inlined_instruction : *cloned_block) {
+      AdaptInlinedInstruction(result_id_map, ir_context, &inlined_instruction);
+    }
+  }
+
+  opt::BasicBlock* block_containing_function_call =
+      ir_context->get_instr_block(function_call_instruction);
+
+  assert(((new_return_block_id == 0) ==
+          called_function->entry()->terminator()->IsReturn()) &&
+         "We should have found a return block unless the function being "
+         "inlined returns in its first block.");
+  if (new_return_block_id != 0) {
+    // Rewrite any OpPhi instructions in the successor block so that they refer
+    // to the new return block instead of the block that originally contained
+    // the function call.
+    ir_context->get_def_use_mgr()->ForEachUse(
+        block_containing_function_call->id(),
+        [ir_context, new_return_block_id, successor_block](
+            opt::Instruction* use_instruction, uint32_t operand_index) {
+          if (use_instruction->opcode() == SpvOpPhi &&
+              ir_context->get_instr_block(use_instruction) == successor_block) {
+            use_instruction->SetOperand(operand_index, {new_return_block_id});
+          }
+        });
+  }
+
+  // Removes the function call instruction and its block termination instruction
+  // from |caller_function|.
+  ir_context->KillInst(block_containing_function_call->terminator());
+  ir_context->KillInst(function_call_instruction);
+
+  // Since the SPIR-V module has changed, no analyses must be validated.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationInlineFunction::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_inline_function() = message_;
+  return result;
+}
+
+bool TransformationInlineFunction::IsSuitableForInlining(
+    opt::IRContext* ir_context, opt::Instruction* function_call_instruction) {
+  // |function_call_instruction| must be defined and must be an OpFunctionCall
+  // instruction.
+  if (!function_call_instruction ||
+      function_call_instruction->opcode() != SpvOpFunctionCall) {
+    return false;
+  }
+
+  // If |function_call_instruction| return type is void, then
+  // |function_call_instruction| must not have uses.
+  if (ir_context->get_type_mgr()
+          ->GetType(function_call_instruction->type_id())
+          ->AsVoid() &&
+      ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) {
+    return false;
+  }
+
+  // |called_function| must not have an early return.
+  auto called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+  if (called_function->HasEarlyReturn()) {
+    return false;
+  }
+
+  // |called_function| must not use OpKill or OpUnreachable.
+  if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationInlineFunction::AdaptInlinedInstruction(
+    const std::map<uint32_t, uint32_t>& result_id_map,
+    opt::IRContext* ir_context,
+    opt::Instruction* instruction_to_be_inlined) const {
+  auto* function_call_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
+  auto* called_function = fuzzerutil::FindFunction(
+      ir_context, function_call_instruction->GetSingleWordInOperand(0));
+
+  const auto* function_call_block =
+      ir_context->get_instr_block(function_call_instruction);
+  assert(function_call_block && "OpFunctionCall must belong to some block");
+
+  // Replaces the operand ids with their mapped result ids.
+  instruction_to_be_inlined->ForEachInId(
+      [called_function, function_call_instruction, &result_id_map,
+       function_call_block](uint32_t* id) {
+        // We are not inlining the entry block of the |called_function|.
+        //
+        // We must check this condition first since we can't use the fresh id
+        // from |result_id_map| even if it has one. This is because that fresh
+        // id will never be added to the module since entry blocks are not
+        // inlined.
+        if (*id == called_function->entry()->id()) {
+          *id = function_call_block->id();
+          return;
+        }
+
+        // If |id| is mapped, then set it to its mapped value.
+        if (result_id_map.count(*id)) {
+          *id = result_id_map.at(*id);
+          return;
+        }
+
+        uint32_t parameter_index = 0;
+        called_function->ForEachParam(
+            [id, function_call_instruction,
+             &parameter_index](opt::Instruction* parameter_instruction) {
+              // If the id is a function parameter, then set it to the
+              // parameter value passed in the function call instruction.
+              if (*id == parameter_instruction->result_id()) {
+                // We do + 1 because the first in-operand for OpFunctionCall is
+                // the function id that is being called.
+                *id = function_call_instruction->GetSingleWordInOperand(
+                    parameter_index + 1);
+              }
+              parameter_index++;
+            });
+      });
+
+  // If |instruction_to_be_inlined| has result id, then set it to its mapped
+  // value.
+  if (instruction_to_be_inlined->HasResultId()) {
+    assert(result_id_map.count(instruction_to_be_inlined->result_id()) &&
+           "Result id must be mapped to a fresh id.");
+    instruction_to_be_inlined->SetResultId(
+        result_id_map.at(instruction_to_be_inlined->result_id()));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    instruction_to_be_inlined->result_id());
+  }
+
+  // The return instruction will be changed into an OpBranch to the basic
+  // block that follows the block containing the function call.
+  if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) {
+    uint32_t successor_block_id =
+        ir_context->get_instr_block(function_call_instruction)
+            ->terminator()
+            ->GetSingleWordInOperand(0);
+    switch (instruction_to_be_inlined->opcode()) {
+      case SpvOpReturn:
+        instruction_to_be_inlined->AddOperand(
+            {SPV_OPERAND_TYPE_ID, {successor_block_id}});
+        break;
+      case SpvOpReturnValue: {
+        instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpCopyObject, function_call_instruction->type_id(),
+            function_call_instruction->result_id(),
+            opt::Instruction::OperandList(
+                {{SPV_OPERAND_TYPE_ID,
+                  {instruction_to_be_inlined->GetSingleWordOperand(0)}}})));
+        instruction_to_be_inlined->SetInOperand(0, {successor_block_id});
+        break;
+      }
+      default:
+        break;
+    }
+    instruction_to_be_inlined->SetOpcode(SpvOpBranch);
+  }
+}
+
+std::unordered_set<uint32_t> TransformationInlineFunction::GetFreshIds() const {
+  std::unordered_set<uint32_t> result;
+  for (auto& pair : message_.result_id_map()) {
+    result.insert(pair.second());
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_inline_function.h b/source/fuzz/transformation_inline_function.h
new file mode 100644
index 0000000..8105d92
--- /dev/null
+++ b/source/fuzz/transformation_inline_function.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_INLINE_FUNCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_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 TransformationInlineFunction : public Transformation {
+ public:
+  explicit TransformationInlineFunction(
+      const protobufs::TransformationInlineFunction& message);
+
+  TransformationInlineFunction(
+      uint32_t function_call_id,
+      const std::map<uint32_t, uint32_t>& result_id_map);
+
+  // - |message_.result_id_map| must map the instructions of the called function
+  //   to fresh ids, unless overflow ids are available.
+  // - |message_.function_call_id| must be an OpFunctionCall instruction.
+  //   It must not have an early return and must not use OpUnreachable or
+  //   OpKill. This is to guard against making the module invalid when the
+  //   caller is inside a continue construct.
+  //   TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3735):
+  //     Allow functions that use OpKill or OpUnreachable to be inlined if the
+  //     function call is not part of a continue construct.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the OpFunctionCall instruction, identified by
+  // |message_.function_call_id|, with a copy of the function's body.
+  // |message_.result_id_map| is used to provide fresh ids for duplicate
+  // instructions.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if |function_call_instruction| is defined, is an
+  // OpFunctionCall instruction, has no uses if its return type is void, has no
+  // early returns and has no uses of OpKill or OpUnreachable.
+  static bool IsSuitableForInlining(
+      opt::IRContext* ir_context, opt::Instruction* function_call_instruction);
+
+ private:
+  protobufs::TransformationInlineFunction message_;
+
+  // Inline |instruction_to_be_inlined| by setting its ids to the corresponding
+  // ids in |result_id_map|.
+  void AdaptInlinedInstruction(
+      const std::map<uint32_t, uint32_t>& result_id_map,
+      opt::IRContext* ir_context, opt::Instruction* instruction) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_INLINE_FUNCTION_H_
diff --git a/source/fuzz/transformation_invert_comparison_operator.cpp b/source/fuzz/transformation_invert_comparison_operator.cpp
index a4e6d8b..ed7358f 100644
--- a/source/fuzz/transformation_invert_comparison_operator.cpp
+++ b/source/fuzz/transformation_invert_comparison_operator.cpp
@@ -174,5 +174,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationInvertComparisonOperator::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_invert_comparison_operator.h b/source/fuzz/transformation_invert_comparison_operator.h
index 9047a14..f00f62b 100644
--- a/source/fuzz/transformation_invert_comparison_operator.h
+++ b/source/fuzz/transformation_invert_comparison_operator.h
@@ -45,6 +45,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if |opcode| is supported by this transformation.
diff --git a/source/fuzz/transformation_load.cpp b/source/fuzz/transformation_load.cpp
index a260c33..f8b3513 100644
--- a/source/fuzz/transformation_load.cpp
+++ b/source/fuzz/transformation_load.cpp
@@ -98,5 +98,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationLoad::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_load.h b/source/fuzz/transformation_load.h
index 4c7c00b..683bba5 100644
--- a/source/fuzz/transformation_load.h
+++ b/source/fuzz/transformation_load.h
@@ -49,6 +49,8 @@
   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:
diff --git a/source/fuzz/transformation_make_vector_operation_dynamic.cpp b/source/fuzz/transformation_make_vector_operation_dynamic.cpp
new file mode 100644
index 0000000..d6d5140
--- /dev/null
+++ b/source/fuzz/transformation_make_vector_operation_dynamic.cpp
@@ -0,0 +1,116 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_make_vector_operation_dynamic.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationMakeVectorOperationDynamic::
+    TransformationMakeVectorOperationDynamic(
+        const spvtools::fuzz::protobufs::
+            TransformationMakeVectorOperationDynamic& message)
+    : message_(message) {}
+
+TransformationMakeVectorOperationDynamic::
+    TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id,
+                                             uint32_t constant_index_id) {
+  message_.set_instruction_result_id(instruction_result_id);
+  message_.set_constant_index_id(constant_index_id);
+}
+
+bool TransformationMakeVectorOperationDynamic::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // |instruction| must be a vector operation.
+  auto instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
+  if (!IsVectorOperation(ir_context, instruction)) {
+    return false;
+  }
+
+  // |constant_index_instruction| must be defined as an integer instruction.
+  auto constant_index_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.constant_index_id());
+  if (!constant_index_instruction || !constant_index_instruction->type_id() ||
+      !ir_context->get_type_mgr()
+           ->GetType(constant_index_instruction->type_id())
+           ->AsInteger()) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationMakeVectorOperationDynamic::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
+
+  // The OpVectorInsertDynamic instruction has the vector and component operands
+  // in reverse order in relation to the OpCompositeInsert corresponding
+  // operands.
+  if (instruction->opcode() == SpvOpCompositeInsert) {
+    std::swap(instruction->GetInOperand(0), instruction->GetInOperand(1));
+  }
+
+  // Sets the literal operand to the equivalent constant.
+  instruction->SetInOperand(
+      instruction->opcode() == SpvOpCompositeExtract ? 1 : 2,
+      {message_.constant_index_id()});
+
+  // Sets the |instruction| opcode to the corresponding vector dynamic opcode.
+  instruction->SetOpcode(instruction->opcode() == SpvOpCompositeExtract
+                             ? SpvOpVectorExtractDynamic
+                             : SpvOpVectorInsertDynamic);
+}
+
+protobufs::Transformation TransformationMakeVectorOperationDynamic::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_make_vector_operation_dynamic() = message_;
+  return result;
+}
+
+bool TransformationMakeVectorOperationDynamic::IsVectorOperation(
+    opt::IRContext* ir_context, opt::Instruction* instruction) {
+  // |instruction| must be defined and must be an OpCompositeExtract/Insert
+  // instruction.
+  if (!instruction || (instruction->opcode() != SpvOpCompositeExtract &&
+                       instruction->opcode() != SpvOpCompositeInsert)) {
+    return false;
+  }
+
+  // The composite must be a vector.
+  auto composite_instruction =
+      ir_context->get_def_use_mgr()->GetDef(instruction->GetSingleWordInOperand(
+          instruction->opcode() == SpvOpCompositeExtract ? 0 : 1));
+  if (!ir_context->get_type_mgr()
+           ->GetType(composite_instruction->type_id())
+           ->AsVector()) {
+    return false;
+  }
+
+  return true;
+}
+
+std::unordered_set<uint32_t>
+TransformationMakeVectorOperationDynamic::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_make_vector_operation_dynamic.h b/source/fuzz/transformation_make_vector_operation_dynamic.h
new file mode 100644
index 0000000..d1765c5
--- /dev/null
+++ b/source/fuzz/transformation_make_vector_operation_dynamic.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MAKE_VECTOR_OPERATION_DYNAMIC_H_
+#define SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_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 TransformationMakeVectorOperationDynamic : public Transformation {
+ public:
+  explicit TransformationMakeVectorOperationDynamic(
+      const protobufs::TransformationMakeVectorOperationDynamic& message);
+
+  TransformationMakeVectorOperationDynamic(uint32_t instruction_result_id,
+                                           uint32_t constant_index_id);
+
+  // - |message_.instruction_result_id| must be the result id of an
+  // OpCompositeExtract/Insert instruction such that the composite operand is a
+  // vector.
+  // - |message_.constant_index_id| must be the result id of an integer
+  // instruction such that its value equals the indexing literal of the
+  // OpCompositeExtract/Insert instruction.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the OpCompositeExtract and OpCompositeInsert instructions with the
+  // OpVectorExtractDynamic and OpVectorInsertDynamic instructions.
+  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 |instruction| is defined, is an OpCompositeExtract/Insert
+  // instruction and the composite operand is a vector.
+  static bool IsVectorOperation(opt::IRContext* ir_context,
+                                opt::Instruction* instruction);
+
+ private:
+  protobufs::TransformationMakeVectorOperationDynamic message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_MAKE_VECTOR_OPERATION_DYNAMIC_H_
diff --git a/source/fuzz/transformation_merge_blocks.cpp b/source/fuzz/transformation_merge_blocks.cpp
index 68ac092..2a9e90c 100644
--- a/source/fuzz/transformation_merge_blocks.cpp
+++ b/source/fuzz/transformation_merge_blocks.cpp
@@ -78,5 +78,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationMergeBlocks::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_merge_blocks.h b/source/fuzz/transformation_merge_blocks.h
index 1dc16d2..d9a0ca0 100644
--- a/source/fuzz/transformation_merge_blocks.h
+++ b/source/fuzz/transformation_merge_blocks.h
@@ -44,6 +44,8 @@
   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:
diff --git a/source/fuzz/transformation_merge_function_returns.cpp b/source/fuzz/transformation_merge_function_returns.cpp
new file mode 100644
index 0000000..90578a2
--- /dev/null
+++ b/source/fuzz/transformation_merge_function_returns.cpp
@@ -0,0 +1,831 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_merge_function_returns.h"
+
+#include "source/fuzz/comparator_deep_blocks_first.h"
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
+    const protobufs::TransformationMergeFunctionReturns& message)
+    : message_(message) {}
+
+TransformationMergeFunctionReturns::TransformationMergeFunctionReturns(
+    uint32_t function_id, uint32_t outer_header_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_outer_return_id(outer_return_id);
+  message_.set_return_val_id(return_val_id);
+  message_.set_any_returnable_val_id(any_returnable_val_id);
+  for (const auto& return_merging_info : returns_merging_info) {
+    *message_.add_return_merging_info() = return_merging_info;
+  }
+}
+
+bool TransformationMergeFunctionReturns::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  auto function = ir_context->GetFunction(message_.function_id());
+  // The function must exist.
+  if (!function) {
+    return false;
+  }
+
+  // The entry block must end in an unconditional branch.
+  if (function->entry()->terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  // The module must contain an OpConstantTrue instruction.
+  if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
+                                        true, false)) {
+    return false;
+  }
+
+  // The module must contain an OpConstantFalse instruction.
+  if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
+                                        false, false)) {
+    return false;
+  }
+
+  // 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()}) {
+    if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
+                                                             &used_fresh_ids)) {
+      return false;
+    }
+  }
+
+  // Check the additional fresh id required if the function is not void.
+  auto function_type = ir_context->get_type_mgr()->GetType(function->type_id());
+  assert(function_type && "The function type should always exist.");
+
+  if (!function_type->AsVoid() &&
+      (!message_.return_val_id() ||
+       !CheckIdIsFreshAndNotUsedByThisTransformation(
+           message_.return_val_id(), ir_context, &used_fresh_ids))) {
+    return false;
+  }
+
+  // Get a map from the types for which ids are available at the end of the
+  // entry block to one of the ids with that type. We compute this here to avoid
+  // potentially doing it multiple times later on.
+  auto types_to_available_ids =
+      GetTypesToIdAvailableAfterEntryBlock(ir_context);
+
+  // Get the reachable return blocks.
+  auto return_blocks =
+      fuzzerutil::GetReachableReturnBlocks(ir_context, message_.function_id());
+
+  // Map each merge block of loops containing reachable return blocks to the
+  // corresponding returning predecessors (all the blocks that, at the end of
+  // the transformation, will branch to the merge block because the function is
+  // returning).
+  std::map<uint32_t, std::set<uint32_t>> merge_blocks_to_returning_preds;
+  for (uint32_t block : return_blocks) {
+    uint32_t merge_block =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(block);
+
+    while (merge_block != 0) {
+      // If we have seen this merge block before, update the corresponding set
+      // and break out of the loop.
+      if (merge_blocks_to_returning_preds.count(merge_block)) {
+        merge_blocks_to_returning_preds[merge_block].emplace(block);
+        break;
+      }
+
+      // If we have not seen this merge block before, add a new entry and walk
+      // up the loop tree.
+      merge_blocks_to_returning_preds.emplace(merge_block,
+                                              std::set<uint32_t>({block}));
+
+      // Walk up the loop tree.
+      block = merge_block;
+      merge_block =
+          ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(merge_block);
+    }
+  }
+
+  // Instructions in the relevant merge blocks must be restricted to OpLabel,
+  // OpPhi and OpBranch.
+  for (const auto& merge_block_entry : merge_blocks_to_returning_preds) {
+    uint32_t merge_block = merge_block_entry.first;
+    bool all_instructions_allowed =
+        ir_context->get_instr_block(merge_block)
+            ->WhileEachInst([](opt::Instruction* inst) {
+              return inst->opcode() == SpvOpLabel ||
+                     inst->opcode() == SpvOpPhi ||
+                     inst->opcode() == SpvOpBranch;
+            });
+    if (!all_instructions_allowed) {
+      return false;
+    }
+  }
+
+  auto merge_blocks_to_info = GetMappingOfMergeBlocksToInfo();
+
+  // For each relevant merge block, check that the correct ids are available.
+  for (const auto& merge_block_entry : merge_blocks_to_returning_preds) {
+    if (!CheckThatTheCorrectIdsAreGivenForMergeBlock(
+            merge_block_entry.first, merge_blocks_to_info,
+            types_to_available_ids, function_type->AsVoid(), ir_context,
+            transformation_context, &used_fresh_ids)) {
+      return false;
+    }
+  }
+
+  // If the function has a non-void return type, and there are merge loops which
+  // contain return instructions, we need to check that either:
+  // - |message_.any_returnable_val_id| exists. In this case, it must have the
+  //   same type as the return type of the function and be available at the end
+  //   of the entry block.
+  // - a suitable id, available at the end of the entry block can be found in
+  //   the module.
+  if (!function_type->AsVoid() && !merge_blocks_to_returning_preds.empty()) {
+    auto returnable_val_def =
+        ir_context->get_def_use_mgr()->GetDef(message_.any_returnable_val_id());
+    if (!returnable_val_def) {
+      // Check if a suitable id can be found in the module.
+      if (types_to_available_ids.count(function->type_id()) == 0) {
+        return false;
+      }
+    } else if (returnable_val_def->type_id() != function->type_id()) {
+      return false;
+    } else if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+                   ir_context, function->entry()->terminator(),
+                   message_.any_returnable_val_id())) {
+      // The id must be available at the end of the entry block.
+      return false;
+    }
+  }
+
+  // Check that adding new predecessors to the relevant merge blocks does not
+  // render any instructions invalid (each id definition must still dominate
+  // each of its uses).
+  if (!CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
+          ir_context, function, merge_blocks_to_returning_preds)) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationMergeFunctionReturns::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto function = ir_context->GetFunction(message_.function_id());
+  auto function_type = ir_context->get_type_mgr()->GetType(function->type_id());
+
+  // Get a map from the types for which ids are available at the end of the
+  // entry block to one of the ids with that type. We compute this here to avoid
+  // potentially doing it multiple times later on.
+  auto types_to_available_ids =
+      GetTypesToIdAvailableAfterEntryBlock(ir_context);
+
+  uint32_t bool_type = fuzzerutil::MaybeGetBoolType(ir_context);
+
+  uint32_t constant_true = fuzzerutil::MaybeGetBoolConstant(
+      ir_context, *transformation_context, true, false);
+
+  uint32_t constant_false = fuzzerutil::MaybeGetBoolConstant(
+      ir_context, *transformation_context, false, false);
+
+  // Get the reachable return blocks.
+  auto return_blocks =
+      fuzzerutil::GetReachableReturnBlocks(ir_context, message_.function_id());
+
+  // Keep a map from the relevant merge blocks to a mapping from each of the
+  // returning predecessors to the corresponding pair (return value,
+  // boolean specifying whether the function is returning). Returning
+  // predecessors are blocks in the loop (not further nested inside loops),
+  // which either return or are merge blocks of nested loops containing return
+  // instructions.
+  std::map<uint32_t, std::map<uint32_t, std::pair<uint32_t, uint32_t>>>
+      merge_blocks_to_returning_predecessors;
+
+  // Initialise the map, mapping each relevant merge block to an empty map.
+  for (uint32_t ret_block_id : return_blocks) {
+    uint32_t merge_block_id =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(ret_block_id);
+
+    while (merge_block_id != 0 &&
+           !merge_blocks_to_returning_predecessors.count(merge_block_id)) {
+      merge_blocks_to_returning_predecessors.emplace(
+          merge_block_id, std::map<uint32_t, std::pair<uint32_t, uint32_t>>());
+      merge_block_id = ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(
+          merge_block_id);
+    }
+  }
+
+  // Get a reference to an instruction with the same type id as the function's
+  // return type, if the type of the function is not void and ther are loops
+  // containing return instructions.
+  uint32_t returnable_val_id = 0;
+  if (!function_type->AsVoid() &&
+      !merge_blocks_to_returning_predecessors.empty()) {
+    // If |message.any_returnable_val_id| can be found in the module, use it.
+    // Otherwise, use another suitable id found in the module.
+    auto returnable_val_def =
+        ir_context->get_def_use_mgr()->GetDef(message_.any_returnable_val_id());
+    returnable_val_id = returnable_val_def
+                            ? returnable_val_def->result_id()
+                            : types_to_available_ids[function->type_id()];
+  }
+
+  // Keep a map from all the new predecessors of the merge block of the new
+  // outer loop, to the related return value ids.
+  std::map<uint32_t, uint32_t> outer_merge_predecessors;
+
+  // Adjust the return blocks and add the related information to the map or
+  // |outer_merge_predecessors| set.
+  for (uint32_t ret_block_id : return_blocks) {
+    auto ret_block = ir_context->get_instr_block(ret_block_id);
+
+    // Get the return value id (if the function is not void).
+    uint32_t ret_val_id =
+        function_type->AsVoid()
+            ? 0
+            : ret_block->terminator()->GetSingleWordInOperand(0);
+
+    uint32_t merge_block_id =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(ret_block_id);
+
+    // Add a new entry to the map corresponding to the merge block of the
+    // innermost enclosing loop (or that of the new outer loop if there is no
+    // enclosing loop).
+    if (merge_block_id != 0) {
+      merge_blocks_to_returning_predecessors[merge_block_id].emplace(
+          ret_block_id,
+          std::pair<uint32_t, uint32_t>(ret_val_id, constant_true));
+    } else {
+      // If there is no enclosing loop, the block will branch to the merge block
+      // of the new outer loop.
+      merge_block_id = message_.outer_return_id();
+      outer_merge_predecessors.emplace(ret_block_id, ret_val_id);
+    }
+
+    // Replace the return instruction with an unconditional branch.
+    ret_block->terminator()->SetOpcode(SpvOpBranch);
+    ret_block->terminator()->SetInOperands(
+        {{SPV_OPERAND_TYPE_ID, {merge_block_id}}});
+  }
+
+  // Get a list of all the relevant merge blocks.
+  std::vector<uint32_t> merge_blocks;
+  merge_blocks.reserve(merge_blocks_to_returning_predecessors.size());
+  for (const auto& entry : merge_blocks_to_returning_predecessors) {
+    merge_blocks.emplace_back(entry.first);
+  }
+
+  // Sort the list so that deeper merge blocks come first.
+  // We need to consider deeper merge blocks first so that, when a merge block
+  // is considered, all the merge blocks enclosed by the corresponding loop have
+  // already been considered and, thus, the mapping from this merge block to the
+  // returning predecessors is complete.
+  std::sort(merge_blocks.begin(), merge_blocks.end(),
+            ComparatorDeepBlocksFirst(ir_context));
+
+  auto merge_blocks_to_info = GetMappingOfMergeBlocksToInfo();
+
+  // Adjust the merge blocks and add the related information to the map or
+  // |outer_merge_predecessors| set.
+  for (uint32_t merge_block_id : merge_blocks) {
+    // Get the info corresponding to |merge_block| from the map, if a
+    // corresponding entry exists. Otherwise use overflow ids and find suitable
+    // ids in the module.
+    protobufs::ReturnMergingInfo* info =
+        merge_blocks_to_info.count(merge_block_id)
+            ? &merge_blocks_to_info[merge_block_id]
+            : nullptr;
+
+    uint32_t is_returning_id =
+        info ? info->is_returning_id()
+             : transformation_context->GetOverflowIdSource()
+                   ->GetNextOverflowId();
+
+    uint32_t maybe_return_val_id = 0;
+    if (!function_type->AsVoid()) {
+      maybe_return_val_id = info ? info->maybe_return_val_id()
+                                 : transformation_context->GetOverflowIdSource()
+                                       ->GetNextOverflowId();
+    }
+
+    // Map from existing OpPhi to overflow ids. If there is no mapping, get an
+    // empty map.
+    auto phi_to_id = info ? fuzzerutil::RepeatedUInt32PairToMap(
+                                *merge_blocks_to_info[merge_block_id]
+                                     .mutable_opphi_to_suitable_id())
+                          : std::map<uint32_t, uint32_t>();
+
+    // Get a reference to the info related to the returning predecessors.
+    const auto& returning_preds =
+        merge_blocks_to_returning_predecessors[merge_block_id];
+
+    // Get a set of the original predecessors.
+    auto preds_list = ir_context->cfg()->preds(merge_block_id);
+    auto preds = std::set<uint32_t>(preds_list.begin(), preds_list.end());
+
+    auto merge_block = ir_context->get_instr_block(merge_block_id);
+
+    // Adjust the existing OpPhi instructions.
+    merge_block->ForEachPhiInst(
+        [&preds, &returning_preds, &phi_to_id,
+         &types_to_available_ids](opt::Instruction* inst) {
+          // We need a placeholder value id. If |phi_to_id| contains a mapping
+          // for this instruction, we use the given id, otherwise a suitable id
+          // for the instruction's type from |types_to_available_ids|.
+          uint32_t placeholder_val_id =
+              phi_to_id.count(inst->result_id())
+                  ? phi_to_id[inst->result_id()]
+                  : types_to_available_ids[inst->type_id()];
+          assert(placeholder_val_id &&
+                 "We should always be able to find a suitable if the "
+                 "transformation is applicable.");
+
+          // Add a pair of operands (placeholder id, new predecessor) for each
+          // new predecessor of the merge block.
+          for (const auto& entry : returning_preds) {
+            // A returning predecessor may already be a predecessor of the
+            // block. In that case, we should not add new operands.
+            // Each entry is in the form (predecessor, {return val, is
+            // returning}).
+            if (!preds.count(entry.first)) {
+              inst->AddOperand({SPV_OPERAND_TYPE_ID, {placeholder_val_id}});
+              inst->AddOperand({SPV_OPERAND_TYPE_ID, {entry.first}});
+            }
+          }
+        });
+
+    // If the function is not void, add a new OpPhi instructions to collect the
+    // return value from the returning predecessors.
+    if (!function_type->AsVoid()) {
+      opt::Instruction::OperandList operand_list;
+
+      // Add two operands (return value, predecessor) for each returning
+      // predecessor.
+      for (auto entry : returning_preds) {
+        // Each entry is in the form (predecessor, {return value,
+        // is returning}).
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {entry.second.first}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {entry.first}});
+      }
+
+      // Add two operands for each original predecessor from which the function
+      // does not return.
+      for (uint32_t original_pred : preds) {
+        // Only add operands if the function cannot be returning from this
+        // block.
+        if (returning_preds.count(original_pred)) {
+          continue;
+        }
+
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {returnable_val_id}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {original_pred}});
+      }
+
+      // Insert the instruction.
+      merge_block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpPhi, function->type_id(), maybe_return_val_id,
+          std::move(operand_list)));
+
+      fuzzerutil::UpdateModuleIdBound(ir_context, maybe_return_val_id);
+    }
+
+    // Add an OpPhi instruction deciding whether the function is returning.
+    {
+      opt::Instruction::OperandList operand_list;
+
+      // Add two operands (return value, is returning) for each returning
+      // predecessor.
+      for (auto entry : returning_preds) {
+        // Each entry is in the form (predecessor, {return value,
+        // is returning}).
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {entry.second.second}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {entry.first}});
+      }
+
+      // Add two operands for each original predecessor from which the function
+      // does not return.
+      for (uint32_t original_pred : preds) {
+        // Only add operands if the function cannot be returning from this
+        // block.
+        if (returning_preds.count(original_pred)) {
+          continue;
+        }
+
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {constant_false}});
+        operand_list.emplace_back(
+            opt::Operand{SPV_OPERAND_TYPE_ID, {original_pred}});
+      }
+
+      // Insert the instruction.
+      merge_block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpPhi, bool_type, is_returning_id,
+          std::move(operand_list)));
+
+      fuzzerutil::UpdateModuleIdBound(ir_context, is_returning_id);
+    }
+
+    // Change the branching instruction of the block.
+    assert(merge_block->terminator()->opcode() == SpvOpBranch &&
+           "Each block should branch unconditionally to the next.");
+
+    // Add a new entry to the map corresponding to the merge block of the
+    // innermost enclosing loop (or that of the new outer loop if there is no
+    // enclosing loop).
+    uint32_t enclosing_merge =
+        ir_context->GetStructuredCFGAnalysis()->LoopMergeBlock(merge_block_id);
+    if (enclosing_merge == 0) {
+      enclosing_merge = message_.outer_return_id();
+      outer_merge_predecessors.emplace(merge_block_id, maybe_return_val_id);
+    } else {
+      merge_blocks_to_returning_predecessors[enclosing_merge].emplace(
+          merge_block_id,
+          std::pair<uint32_t, uint32_t>(maybe_return_val_id, is_returning_id));
+    }
+
+    // Get the current successor.
+    uint32_t original_succ =
+        merge_block->terminator()->GetSingleWordInOperand(0);
+    // Leave the instruction as it is if the block already branches to the merge
+    // block of the enclosing loop.
+    if (original_succ == enclosing_merge) {
+      continue;
+    }
+
+    // The block should branch to |enclosing_merge| if |is_returning_id| is
+    // true, to |original_succ| otherwise.
+    merge_block->terminator()->SetOpcode(SpvOpBranchConditional);
+    merge_block->terminator()->SetInOperands(
+        {{SPV_OPERAND_TYPE_ID, {is_returning_id}},
+         {SPV_OPERAND_TYPE_ID, {enclosing_merge}},
+         {SPV_OPERAND_TYPE_ID, {original_succ}}});
+  }
+
+  assert(function->entry()->terminator()->opcode() == SpvOpBranch &&
+         "The entry block should branch unconditionally to another block.");
+  uint32_t block_after_entry =
+      function->entry()->terminator()->GetSingleWordInOperand(0);
+
+  // Create the header for the new outer loop.
+  auto outer_loop_header =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.outer_header_id(),
+          opt::Instruction::OperandList()));
+
+  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.
+  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_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).
+  outer_loop_header->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranchConditional, 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()}}}));
+
+  // Insert the header right after the entry block.
+  function->InsertBasicBlockAfter(std::move(outer_loop_header),
+                                  function->entry().get());
+
+  // Update the branching instruction of the entry block.
+  function->entry()->terminator()->SetInOperands(
+      {{SPV_OPERAND_TYPE_ID, {message_.outer_header_id()}}});
+
+  // If the entry block is referenced in an OpPhi instruction, the header for
+  // the new loop should be referenced instead.
+  ir_context->get_def_use_mgr()->ForEachUse(
+      function->entry()->id(),
+      [this](opt::Instruction* use_instruction, uint32_t use_operand_index) {
+        if (use_instruction->opcode() == SpvOpPhi) {
+          use_instruction->SetOperand(use_operand_index,
+                                      {message_.outer_header_id()});
+        }
+      });
+
+  // Create the merge block for the loop (and return block for the function).
+  auto outer_return_block =
+      MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, message_.outer_return_id(),
+          opt::Instruction::OperandList()));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.outer_return_id());
+
+  // If the function is not void, insert an instruction to collect the return
+  // value from the predecessors and an OpReturnValue instruction.
+  if (!function_type->AsVoid()) {
+    opt::Instruction::OperandList operand_list;
+
+    // Add two operands (return value, predecessor) for each predecessor.
+    for (auto entry : outer_merge_predecessors) {
+      // Each entry is in the form (predecessor, return value).
+      operand_list.emplace_back(
+          opt::Operand{SPV_OPERAND_TYPE_ID, {entry.second}});
+      operand_list.emplace_back(
+          opt::Operand{SPV_OPERAND_TYPE_ID, {entry.first}});
+    }
+
+    // Insert the OpPhi instruction.
+    outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpPhi, function->type_id(), message_.return_val_id(),
+        std::move(operand_list)));
+
+    fuzzerutil::UpdateModuleIdBound(ir_context, message_.return_val_id());
+
+    // Insert the OpReturnValue instruction.
+    outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpReturnValue, 0, 0,
+        opt::Instruction::OperandList{
+            {SPV_OPERAND_TYPE_ID, {message_.return_val_id()}}}));
+  } else {
+    // Insert an OpReturn instruction (the function is void).
+    outer_return_block->AddInstruction(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpReturn, 0, 0, opt::Instruction::OperandList{}));
+  }
+
+  // Insert the new return block at the end of the function.
+  outer_return_block->SetParent(function);
+  function->AddBasicBlock(std::move(outer_return_block));
+
+  // All analyses must be invalidated because the structure of the module was
+  // changed.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+std::unordered_set<uint32_t> TransformationMergeFunctionReturns::GetFreshIds()
+    const {
+  std::unordered_set<uint32_t> result;
+  result.emplace(message_.outer_header_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()) {
+    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()) {
+      result.emplace(merging_info.maybe_return_val_id());
+    }
+  }
+
+  return result;
+}
+
+protobufs::Transformation TransformationMergeFunctionReturns::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_merge_function_returns() = message_;
+  return result;
+}
+
+std::map<uint32_t, protobufs::ReturnMergingInfo>
+TransformationMergeFunctionReturns::GetMappingOfMergeBlocksToInfo() const {
+  std::map<uint32_t, protobufs::ReturnMergingInfo> result;
+  for (const auto& info : message_.return_merging_info()) {
+    result.emplace(info.merge_block_id(), info);
+  }
+  return result;
+}
+
+std::map<uint32_t, uint32_t>
+TransformationMergeFunctionReturns::GetTypesToIdAvailableAfterEntryBlock(
+    opt::IRContext* ir_context) const {
+  std::map<uint32_t, uint32_t> result;
+  // Consider all global declarations
+  for (auto& global : ir_context->module()->types_values()) {
+    if (global.HasResultId() && global.type_id()) {
+      result.emplace(global.type_id(), global.result_id());
+    }
+  }
+
+  auto function = ir_context->GetFunction(message_.function_id());
+  assert(function && "The function must exist.");
+
+  // Consider all function parameters
+  function->ForEachParam([&result](opt::Instruction* param) {
+    if (param->HasResultId() && param->type_id()) {
+      result.emplace(param->type_id(), param->result_id());
+    }
+  });
+
+  // Consider all the instructions in the entry block.
+  for (auto& inst : *function->entry()) {
+    if (inst.HasResultId() && inst.type_id()) {
+      result.emplace(inst.type_id(), inst.result_id());
+    }
+  }
+
+  return result;
+}
+
+bool TransformationMergeFunctionReturns::
+    CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
+        opt::IRContext* ir_context, const opt::Function* function,
+        const std::map<uint32_t, std::set<uint32_t>>&
+            merge_blocks_to_new_predecessors) {
+  for (const auto& merge_block_entry : merge_blocks_to_new_predecessors) {
+    uint32_t merge_block = merge_block_entry.first;
+    const auto& returning_preds = merge_block_entry.second;
+
+    // Find a list of blocks in which there might be problematic definitions.
+    // These are all the blocks that dominate the merge block but do not
+    // dominate all of the new predecessors.
+    std::vector<opt::BasicBlock*> problematic_blocks;
+
+    auto dominator_analysis = ir_context->GetDominatorAnalysis(function);
+
+    // Start from the immediate dominator of the merge block.
+    auto current_block = dominator_analysis->ImmediateDominator(merge_block);
+    assert(current_block &&
+           "Each merge block should have at least one dominator.");
+
+    for (uint32_t pred : returning_preds) {
+      while (!dominator_analysis->Dominates(current_block->id(), pred)) {
+        // The current block does not dominate all of the new predecessor
+        // blocks, so it might be problematic.
+        problematic_blocks.emplace_back(current_block);
+
+        // Walk up the dominator tree.
+        current_block = dominator_analysis->ImmediateDominator(current_block);
+        assert(current_block &&
+               "We should be able to find a dominator for all the blocks, "
+               "since they must all be dominated at least by the header.");
+      }
+    }
+
+    // Identify the loop header corresponding to the merge block.
+    uint32_t loop_header =
+        fuzzerutil::GetLoopFromMergeBlock(ir_context, merge_block);
+
+    // For all the ids defined in blocks inside |problematic_blocks|, check that
+    // all their uses are either:
+    // - inside the loop (or in the loop header). If this is the case, the path
+    //   from the definition to the use does not go through the merge block, so
+    //   adding new predecessor to it is not a problem.
+    // - inside an OpPhi instruction in the merge block. If this is the case,
+    //   the definition does not need to dominate the merge block.
+    for (auto block : problematic_blocks) {
+      assert((block->id() == loop_header ||
+              ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
+                  block->id()) == loop_header) &&
+             "The problematic blocks should all be inside the loop (also "
+             "considering the header).");
+      bool dominance_rules_maintained =
+          block->WhileEachInst([ir_context, loop_header,
+                                merge_block](opt::Instruction* instruction) {
+            // Instruction without a result id do not cause any problems.
+            if (!instruction->HasResultId()) {
+              return true;
+            }
+
+            // Check that all the uses of the id are inside the loop.
+            return ir_context->get_def_use_mgr()->WhileEachUse(
+                instruction->result_id(),
+                [ir_context, loop_header, merge_block](
+                    opt::Instruction* inst_use, uint32_t /* unused */) {
+                  uint32_t block_use =
+                      ir_context->get_instr_block(inst_use)->id();
+
+                  // The usage is OK if it is inside the loop (including the
+                  // header).
+                  if (block_use == loop_header ||
+                      ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
+                          block_use)) {
+                    return true;
+                  }
+
+                  // The usage is OK if it is inside an OpPhi instruction in the
+                  // merge block.
+                  return block_use == merge_block &&
+                         inst_use->opcode() == SpvOpPhi;
+                });
+          });
+
+      // If not all instructions in the block satisfy the requirement, the
+      // transformation is not applicable.
+      if (!dominance_rules_maintained) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool TransformationMergeFunctionReturns::
+    CheckThatTheCorrectIdsAreGivenForMergeBlock(
+        uint32_t merge_block,
+        const std::map<uint32_t, protobufs::ReturnMergingInfo>&
+            merge_blocks_to_info,
+        const std::map<uint32_t, uint32_t>& types_to_available_id,
+        bool function_is_void, opt::IRContext* ir_context,
+        const TransformationContext& transformation_context,
+        std::set<uint32_t>* used_fresh_ids) {
+  // A map from OpPhi ids to ids of the same type available at the beginning
+  // of the merge block.
+  std::map<uint32_t, uint32_t> phi_to_id;
+
+  if (merge_blocks_to_info.count(merge_block) > 0) {
+    // If the map contains an entry for the merge block, check that the fresh
+    // ids are fresh and distinct.
+    auto info = merge_blocks_to_info.at(merge_block);
+    if (!info.is_returning_id() ||
+        !CheckIdIsFreshAndNotUsedByThisTransformation(
+            info.is_returning_id(), ir_context, used_fresh_ids)) {
+      return false;
+    }
+
+    if (!function_is_void &&
+        (!info.maybe_return_val_id() ||
+         !CheckIdIsFreshAndNotUsedByThisTransformation(
+             info.maybe_return_val_id(), ir_context, used_fresh_ids))) {
+      return false;
+    }
+
+    // Get the mapping from OpPhis to suitable ids.
+    phi_to_id = fuzzerutil::RepeatedUInt32PairToMap(
+        *info.mutable_opphi_to_suitable_id());
+  } else {
+    // If the map does not contain an entry for the merge block, check that
+    // overflow ids are available.
+    if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+      return false;
+    }
+  }
+
+  // For each OpPhi instruction, check that a suitable placeholder id is
+  // available.
+  bool suitable_info_for_phi =
+      ir_context->get_instr_block(merge_block)
+          ->WhileEachPhiInst([ir_context, &phi_to_id,
+                              &types_to_available_id](opt::Instruction* inst) {
+            if (phi_to_id.count(inst->result_id()) > 0) {
+              // If there exists a mapping for this instruction and the
+              // placeholder id exists in the module, check that it has the
+              // correct type and it is available before the instruction.
+              auto placeholder_def = ir_context->get_def_use_mgr()->GetDef(
+                  phi_to_id[inst->result_id()]);
+              if (placeholder_def) {
+                if (inst->type_id() != placeholder_def->type_id()) {
+                  return false;
+                }
+                if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+                        ir_context, inst, placeholder_def->result_id())) {
+                  return false;
+                }
+
+                return true;
+              }
+            }
+
+            // If there is no mapping, check if there is a suitable id
+            // available at the end of the entry block.
+            return types_to_available_id.count(inst->type_id()) > 0;
+          });
+
+  if (!suitable_info_for_phi) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_merge_function_returns.h b/source/fuzz/transformation_merge_function_returns.h
new file mode 100644
index 0000000..4b29936
--- /dev/null
+++ b/source/fuzz/transformation_merge_function_returns.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MERGE_FUNCTION_RETURNS_
+#define SOURCE_FUZZ_TRANSFORMATION_MERGE_FUNCTION_RETURNS_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+class TransformationMergeFunctionReturns : public Transformation {
+ public:
+  explicit TransformationMergeFunctionReturns(
+      const protobufs::TransformationMergeFunctionReturns& message);
+
+  TransformationMergeFunctionReturns(
+      uint32_t function_id, uint32_t outer_header_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_.function_id| is the id of a function.
+  // - The entry block of |message_.function_id| branches unconditionally to
+  //   another block.
+  // - |message_.any_returnable_val_id| is an id whose type is the same as the
+  //   return type of the function and which is available at the end of the
+  //   entry block. If this id is not found in the module, the transformation
+  //   will try to find a suitable one.
+  //   If the function is void, or no loops in the function contain return
+  //   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.
+  // - For all merge blocks of reachable loops that contain return statements,
+  //   either:
+  //   - a mapping is provided in |message_.return_merging_info|, all of the
+  //     corresponding fresh ids are valid and, for each OpPhi instruction in
+  //     the block, there is a mapping to an available id of the same type in
+  //     |opphi_to_suitable_id| or a suitable id, available at the end of the
+  //     entry block, can be found in the module.
+  //   - there is no mapping, but overflow ids are available and, for every
+  //     OpPhi instruction in the merge blocks that need to be modified, a
+  //     suitable id, available at the end of the entry block, can be found.
+  // - The addition of new predecessors to the relevant merge blocks does not
+  //   cause any id use to be invalid (i.e. every id must dominate all its uses
+  //   even after the transformation has added new branches).
+  // - All of the fresh ids that are provided and needed by the transformation
+  //   are valid.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Changes the function so that there is only one reachable return
+  // instruction. The function is enclosed by an outer loop, whose merge block
+  // is the new return block. All existing return statements are replaced by
+  // branch instructions to the merge block of the loop enclosing them, and
+  // OpPhi instructions are used to keep track of the return value and of
+  // whether the function is returning.
+  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:
+  // Returns a map from merge block ids to the corresponding info in
+  // |message_.return_merging_info|.
+  std::map<uint32_t, protobufs::ReturnMergingInfo>
+  GetMappingOfMergeBlocksToInfo() const;
+
+  // Returns a map from type ids to an id with that type and which is available
+  // at the end of the entry block of |message_.function_id|.
+  // Assumes that the function exists.
+  std::map<uint32_t, uint32_t> GetTypesToIdAvailableAfterEntryBlock(
+      opt::IRContext* ir_context) const;
+
+  // Returns true if adding new predecessors to the given loop merge blocks
+  // does not render any instructions invalid (each id definition must still
+  // dominate all of its uses). The loop merge blocks and corresponding new
+  // predecessors to consider are given in |merge_blocks_to_new_predecessors|.
+  // All of the new predecessors are assumed to be inside the loop associated
+  // with the corresponding loop merge block.
+  static bool CheckDefinitionsStillDominateUsesAfterAddingNewPredecessors(
+      opt::IRContext* ir_context, const opt::Function* function,
+      const std::map<uint32_t, std::set<uint32_t>>&
+          merge_blocks_to_new_predecessors);
+
+  // Returns true if the required ids for |merge_block| are provided in the
+  // |merge_blocks_to_info| map, or if ids of the suitable type can be found.
+  static bool CheckThatTheCorrectIdsAreGivenForMergeBlock(
+      uint32_t merge_block,
+      const std::map<uint32_t, protobufs::ReturnMergingInfo>&
+          merge_blocks_to_info,
+      const std::map<uint32_t, uint32_t>& types_to_available_id,
+      bool function_is_void, opt::IRContext* ir_context,
+      const TransformationContext& transformation_context,
+      std::set<uint32_t>* used_fresh_ids);
+
+  protobufs::TransformationMergeFunctionReturns message_;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_MERGE_FUNCTION_RETURNS_
diff --git a/source/fuzz/transformation_move_block_down.cpp b/source/fuzz/transformation_move_block_down.cpp
index 6c71ab7..c5ed433 100644
--- a/source/fuzz/transformation_move_block_down.cpp
+++ b/source/fuzz/transformation_move_block_down.cpp
@@ -105,5 +105,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationMoveBlockDown::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_move_block_down.h b/source/fuzz/transformation_move_block_down.h
index 7551c38..82f2599 100644
--- a/source/fuzz/transformation_move_block_down.h
+++ b/source/fuzz/transformation_move_block_down.h
@@ -44,6 +44,8 @@
   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:
diff --git a/source/fuzz/transformation_move_instruction_down.cpp b/source/fuzz/transformation_move_instruction_down.cpp
new file mode 100644
index 0000000..dec0578
--- /dev/null
+++ b/source/fuzz/transformation_move_instruction_down.cpp
@@ -0,0 +1,734 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_move_instruction_down.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "spirv/unified1/GLSL.std.450.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+const char* const kExtensionSetName = "GLSL.std.450";
+
+std::string GetExtensionSet(opt::IRContext* ir_context,
+                            const opt::Instruction& op_ext_inst) {
+  assert(op_ext_inst.opcode() == SpvOpExtInst && "Wrong opcode");
+
+  const auto* ext_inst_import = ir_context->get_def_use_mgr()->GetDef(
+      op_ext_inst.GetSingleWordInOperand(0));
+  assert(ext_inst_import && "Extension set is not imported");
+
+  return ext_inst_import->GetInOperand(0).AsString();
+}
+
+}  // namespace
+
+TransformationMoveInstructionDown::TransformationMoveInstructionDown(
+    const protobufs::TransformationMoveInstructionDown& message)
+    : message_(message) {}
+
+TransformationMoveInstructionDown::TransformationMoveInstructionDown(
+    const protobufs::InstructionDescriptor& instruction) {
+  *message_.mutable_instruction() = instruction;
+}
+
+bool TransformationMoveInstructionDown::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // |instruction| must be valid.
+  auto* inst = FindInstruction(message_.instruction(), ir_context);
+  if (!inst) {
+    return false;
+  }
+
+  // Instruction's opcode must be supported by this transformation.
+  if (!IsInstructionSupported(ir_context, *inst)) {
+    return false;
+  }
+
+  auto* inst_block = ir_context->get_instr_block(inst);
+  assert(inst_block &&
+         "Global instructions and function parameters are not supported");
+
+  auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst);
+  assert(inst_it != inst_block->end() &&
+         "Can't get an iterator for the instruction");
+
+  // |instruction| can't be the last instruction in the block.
+  auto successor_it = ++inst_it;
+  if (successor_it == inst_block->end()) {
+    return false;
+  }
+
+  // We don't risk swapping a memory instruction with an unsupported one.
+  if (!IsSimpleInstruction(ir_context, *inst) &&
+      !IsInstructionSupported(ir_context, *successor_it)) {
+    return false;
+  }
+
+  // It must be safe to swap the instructions without changing the semantics of
+  // the module.
+  if (IsInstructionSupported(ir_context, *successor_it) &&
+      !CanSafelySwapInstructions(ir_context, *inst, *successor_it,
+                                 *transformation_context.GetFactManager())) {
+    return false;
+  }
+
+  // Check that we can insert |instruction| after |inst_it|.
+  auto successors_successor_it = ++inst_it;
+  if (successors_successor_it == inst_block->end() ||
+      !fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(),
+                                                    successors_successor_it)) {
+    return false;
+  }
+
+  // Check that |instruction|'s successor doesn't depend on the |instruction|.
+  if (inst->result_id()) {
+    for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) {
+      const auto& operand = successor_it->GetInOperand(i);
+      if (spvIsInIdType(operand.type) &&
+          operand.words[0] == inst->result_id()) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
+void TransformationMoveInstructionDown::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto* inst = FindInstruction(message_.instruction(), ir_context);
+  assert(inst &&
+         "The instruction should've been validated in the IsApplicable");
+
+  auto inst_it = fuzzerutil::GetIteratorForInstruction(
+      ir_context->get_instr_block(inst), inst);
+
+  // Move the instruction down in the block.
+  inst->InsertAfter(&*++inst_it);
+
+  ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_move_instruction_down() = message_;
+  return result;
+}
+
+bool TransformationMoveInstructionDown::IsInstructionSupported(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
+  //  Add support for more instructions here.
+  return IsSimpleInstruction(ir_context, inst) ||
+         IsMemoryInstruction(ir_context, inst) || IsBarrierInstruction(inst);
+}
+
+bool TransformationMoveInstructionDown::IsSimpleInstruction(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  switch (inst.opcode()) {
+    case SpvOpNop:
+    case SpvOpUndef:
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+      // OpAccessChain and OpInBoundsAccessChain are considered simple
+      // instructions since they result in a pointer to the object in memory,
+      // not the object itself.
+    case SpvOpVectorExtractDynamic:
+    case SpvOpVectorInsertDynamic:
+    case SpvOpVectorShuffle:
+    case SpvOpCompositeConstruct:
+    case SpvOpCompositeExtract:
+    case SpvOpCompositeInsert:
+    case SpvOpCopyObject:
+    case SpvOpTranspose:
+    case SpvOpConvertFToU:
+    case SpvOpConvertFToS:
+    case SpvOpConvertSToF:
+    case SpvOpConvertUToF:
+    case SpvOpUConvert:
+    case SpvOpSConvert:
+    case SpvOpFConvert:
+    case SpvOpQuantizeToF16:
+    case SpvOpSatConvertSToU:
+    case SpvOpSatConvertUToS:
+    case SpvOpBitcast:
+    case SpvOpSNegate:
+    case SpvOpFNegate:
+    case SpvOpIAdd:
+    case SpvOpFAdd:
+    case SpvOpISub:
+    case SpvOpFSub:
+    case SpvOpIMul:
+    case SpvOpFMul:
+    case SpvOpUDiv:
+    case SpvOpSDiv:
+    case SpvOpFDiv:
+    case SpvOpUMod:
+    case SpvOpSRem:
+    case SpvOpSMod:
+    case SpvOpFRem:
+    case SpvOpFMod:
+    case SpvOpVectorTimesScalar:
+    case SpvOpMatrixTimesScalar:
+    case SpvOpVectorTimesMatrix:
+    case SpvOpMatrixTimesVector:
+    case SpvOpMatrixTimesMatrix:
+    case SpvOpOuterProduct:
+    case SpvOpDot:
+    case SpvOpIAddCarry:
+    case SpvOpISubBorrow:
+    case SpvOpUMulExtended:
+    case SpvOpSMulExtended:
+    case SpvOpAny:
+    case SpvOpAll:
+    case SpvOpIsNan:
+    case SpvOpIsInf:
+    case SpvOpIsFinite:
+    case SpvOpIsNormal:
+    case SpvOpSignBitSet:
+    case SpvOpLessOrGreater:
+    case SpvOpOrdered:
+    case SpvOpUnordered:
+    case SpvOpLogicalEqual:
+    case SpvOpLogicalNotEqual:
+    case SpvOpLogicalOr:
+    case SpvOpLogicalAnd:
+    case SpvOpLogicalNot:
+    case SpvOpSelect:
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    case SpvOpUGreaterThan:
+    case SpvOpSGreaterThan:
+    case SpvOpUGreaterThanEqual:
+    case SpvOpSGreaterThanEqual:
+    case SpvOpULessThan:
+    case SpvOpSLessThan:
+    case SpvOpULessThanEqual:
+    case SpvOpSLessThanEqual:
+    case SpvOpFOrdEqual:
+    case SpvOpFUnordEqual:
+    case SpvOpFOrdNotEqual:
+    case SpvOpFUnordNotEqual:
+    case SpvOpFOrdLessThan:
+    case SpvOpFUnordLessThan:
+    case SpvOpFOrdGreaterThan:
+    case SpvOpFUnordGreaterThan:
+    case SpvOpFOrdLessThanEqual:
+    case SpvOpFUnordLessThanEqual:
+    case SpvOpFOrdGreaterThanEqual:
+    case SpvOpFUnordGreaterThanEqual:
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+    case SpvOpBitFieldInsert:
+    case SpvOpBitFieldSExtract:
+    case SpvOpBitFieldUExtract:
+    case SpvOpBitReverse:
+    case SpvOpBitCount:
+    case SpvOpCopyLogical:
+      return true;
+    case SpvOpExtInst: {
+      const auto* ext_inst_import =
+          ir_context->get_def_use_mgr()->GetDef(inst.GetSingleWordInOperand(0));
+
+      if (ext_inst_import->GetInOperand(0).AsString() != kExtensionSetName) {
+        return false;
+      }
+
+      switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
+        case GLSLstd450Round:
+        case GLSLstd450RoundEven:
+        case GLSLstd450Trunc:
+        case GLSLstd450FAbs:
+        case GLSLstd450SAbs:
+        case GLSLstd450FSign:
+        case GLSLstd450SSign:
+        case GLSLstd450Floor:
+        case GLSLstd450Ceil:
+        case GLSLstd450Fract:
+        case GLSLstd450Radians:
+        case GLSLstd450Degrees:
+        case GLSLstd450Sin:
+        case GLSLstd450Cos:
+        case GLSLstd450Tan:
+        case GLSLstd450Asin:
+        case GLSLstd450Acos:
+        case GLSLstd450Atan:
+        case GLSLstd450Sinh:
+        case GLSLstd450Cosh:
+        case GLSLstd450Tanh:
+        case GLSLstd450Asinh:
+        case GLSLstd450Acosh:
+        case GLSLstd450Atanh:
+        case GLSLstd450Atan2:
+        case GLSLstd450Pow:
+        case GLSLstd450Exp:
+        case GLSLstd450Log:
+        case GLSLstd450Exp2:
+        case GLSLstd450Log2:
+        case GLSLstd450Sqrt:
+        case GLSLstd450InverseSqrt:
+        case GLSLstd450Determinant:
+        case GLSLstd450MatrixInverse:
+        case GLSLstd450ModfStruct:
+        case GLSLstd450FMin:
+        case GLSLstd450UMin:
+        case GLSLstd450SMin:
+        case GLSLstd450FMax:
+        case GLSLstd450UMax:
+        case GLSLstd450SMax:
+        case GLSLstd450FClamp:
+        case GLSLstd450UClamp:
+        case GLSLstd450SClamp:
+        case GLSLstd450FMix:
+        case GLSLstd450IMix:
+        case GLSLstd450Step:
+        case GLSLstd450SmoothStep:
+        case GLSLstd450Fma:
+        case GLSLstd450FrexpStruct:
+        case GLSLstd450Ldexp:
+        case GLSLstd450PackSnorm4x8:
+        case GLSLstd450PackUnorm4x8:
+        case GLSLstd450PackSnorm2x16:
+        case GLSLstd450PackUnorm2x16:
+        case GLSLstd450PackHalf2x16:
+        case GLSLstd450PackDouble2x32:
+        case GLSLstd450UnpackSnorm2x16:
+        case GLSLstd450UnpackUnorm2x16:
+        case GLSLstd450UnpackHalf2x16:
+        case GLSLstd450UnpackSnorm4x8:
+        case GLSLstd450UnpackUnorm4x8:
+        case GLSLstd450UnpackDouble2x32:
+        case GLSLstd450Length:
+        case GLSLstd450Distance:
+        case GLSLstd450Cross:
+        case GLSLstd450Normalize:
+        case GLSLstd450FaceForward:
+        case GLSLstd450Reflect:
+        case GLSLstd450Refract:
+        case GLSLstd450FindILsb:
+        case GLSLstd450FindSMsb:
+        case GLSLstd450FindUMsb:
+        case GLSLstd450NMin:
+        case GLSLstd450NMax:
+        case GLSLstd450NClamp:
+          return true;
+        default:
+          return false;
+      }
+    }
+    default:
+      return false;
+  }
+}
+
+bool TransformationMoveInstructionDown::IsMemoryReadInstruction(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  switch (inst.opcode()) {
+      // Some simple instructions.
+    case SpvOpLoad:
+    case SpvOpCopyMemory:
+      // Image instructions.
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageFetch:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+    case SpvOpImageRead:
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod:
+    case SpvOpImageSparseSampleProjImplicitLod:
+    case SpvOpImageSparseSampleProjExplicitLod:
+    case SpvOpImageSparseSampleProjDrefImplicitLod:
+    case SpvOpImageSparseSampleProjDrefExplicitLod:
+    case SpvOpImageSparseFetch:
+    case SpvOpImageSparseGather:
+    case SpvOpImageSparseDrefGather:
+    case SpvOpImageSparseRead:
+      // Atomic instructions.
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFlagTestAndSet:
+      return true;
+      // Extensions.
+    case SpvOpExtInst: {
+      if (GetExtensionSet(ir_context, inst) != kExtensionSetName) {
+        return false;
+      }
+
+      switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
+        case GLSLstd450InterpolateAtCentroid:
+        case GLSLstd450InterpolateAtOffset:
+        case GLSLstd450InterpolateAtSample:
+          return true;
+        default:
+          return false;
+      }
+    }
+    default:
+      return false;
+  }
+}
+
+uint32_t TransformationMoveInstructionDown::GetMemoryReadTarget(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  (void)ir_context;  // |ir_context| is only used in assertions.
+  assert(IsMemoryReadInstruction(ir_context, inst) &&
+         "|inst| is not a memory read instruction");
+
+  switch (inst.opcode()) {
+      // Simple instructions.
+    case SpvOpLoad:
+      // Image instructions.
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageFetch:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
+    case SpvOpImageRead:
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod:
+    case SpvOpImageSparseSampleProjImplicitLod:
+    case SpvOpImageSparseSampleProjExplicitLod:
+    case SpvOpImageSparseSampleProjDrefImplicitLod:
+    case SpvOpImageSparseSampleProjDrefExplicitLod:
+    case SpvOpImageSparseFetch:
+    case SpvOpImageSparseGather:
+    case SpvOpImageSparseDrefGather:
+    case SpvOpImageSparseRead:
+      // Atomic instructions.
+    case SpvOpAtomicLoad:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFlagTestAndSet:
+      return inst.GetSingleWordInOperand(0);
+    case SpvOpCopyMemory:
+      return inst.GetSingleWordInOperand(1);
+    case SpvOpExtInst: {
+      assert(GetExtensionSet(ir_context, inst) == kExtensionSetName &&
+             "Extension set is not supported");
+
+      switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
+        case GLSLstd450InterpolateAtCentroid:
+        case GLSLstd450InterpolateAtOffset:
+        case GLSLstd450InterpolateAtSample:
+          return inst.GetSingleWordInOperand(2);
+        default:
+          // This assertion will fail if not all memory read extension
+          // instructions are handled in the switch.
+          assert(false && "Not all memory opcodes are handled");
+          return 0;
+      }
+    }
+    default:
+      // This assertion will fail if not all memory read opcodes are handled in
+      // the switch.
+      assert(false && "Not all memory opcodes are handled");
+      return 0;
+  }
+}
+
+bool TransformationMoveInstructionDown::IsMemoryWriteInstruction(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  switch (inst.opcode()) {
+      // Simple Instructions.
+    case SpvOpStore:
+    case SpvOpCopyMemory:
+      // Image instructions.
+    case SpvOpImageWrite:
+      // Atomic instructions.
+    case SpvOpAtomicStore:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFlagTestAndSet:
+    case SpvOpAtomicFlagClear:
+      return true;
+      // Extensions.
+    case SpvOpExtInst: {
+      if (GetExtensionSet(ir_context, inst) != kExtensionSetName) {
+        return false;
+      }
+
+      auto extension = static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1));
+      return extension == GLSLstd450Modf || extension == GLSLstd450Frexp;
+    }
+    default:
+      return false;
+  }
+}
+
+uint32_t TransformationMoveInstructionDown::GetMemoryWriteTarget(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  (void)ir_context;  // |ir_context| is only used in assertions.
+  assert(IsMemoryWriteInstruction(ir_context, inst) &&
+         "|inst| is not a memory write instruction");
+
+  switch (inst.opcode()) {
+    case SpvOpStore:
+    case SpvOpCopyMemory:
+    case SpvOpImageWrite:
+    case SpvOpAtomicStore:
+    case SpvOpAtomicExchange:
+    case SpvOpAtomicCompareExchange:
+    case SpvOpAtomicCompareExchangeWeak:
+    case SpvOpAtomicIIncrement:
+    case SpvOpAtomicIDecrement:
+    case SpvOpAtomicIAdd:
+    case SpvOpAtomicISub:
+    case SpvOpAtomicSMin:
+    case SpvOpAtomicUMin:
+    case SpvOpAtomicSMax:
+    case SpvOpAtomicUMax:
+    case SpvOpAtomicAnd:
+    case SpvOpAtomicOr:
+    case SpvOpAtomicXor:
+    case SpvOpAtomicFlagTestAndSet:
+    case SpvOpAtomicFlagClear:
+      return inst.GetSingleWordInOperand(0);
+    case SpvOpExtInst: {
+      assert(GetExtensionSet(ir_context, inst) == kExtensionSetName &&
+             "Extension set is not supported");
+
+      switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
+        case GLSLstd450Modf:
+        case GLSLstd450Frexp:
+          return inst.GetSingleWordInOperand(3);
+        default:
+          // This assertion will fail if not all memory write extension
+          // instructions are handled in the switch.
+          assert(false && "Not all opcodes are handled");
+          return 0;
+      }
+    }
+    default:
+      // This assertion will fail if not all memory write opcodes are handled in
+      // the switch.
+      assert(false && "Not all opcodes are handled");
+      return 0;
+  }
+}
+
+bool TransformationMoveInstructionDown::IsMemoryInstruction(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  return IsMemoryReadInstruction(ir_context, inst) ||
+         IsMemoryWriteInstruction(ir_context, inst);
+}
+
+bool TransformationMoveInstructionDown::IsBarrierInstruction(
+    const opt::Instruction& inst) {
+  switch (inst.opcode()) {
+    case SpvOpMemoryBarrier:
+    case SpvOpControlBarrier:
+    case SpvOpMemoryNamedBarrier:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool TransformationMoveInstructionDown::CanSafelySwapInstructions(
+    opt::IRContext* ir_context, const opt::Instruction& a,
+    const opt::Instruction& b, const FactManager& fact_manager) {
+  assert(IsInstructionSupported(ir_context, a) &&
+         IsInstructionSupported(ir_context, b) &&
+         "Both opcodes must be supported");
+
+  // One of opcodes is simple - we can swap them without any side-effects.
+  if (IsSimpleInstruction(ir_context, a) ||
+      IsSimpleInstruction(ir_context, b)) {
+    return true;
+  }
+
+  // Both parameters are either memory instruction or barriers.
+
+  // One of the opcodes is a barrier - can't swap them.
+  if (IsBarrierInstruction(a) || IsBarrierInstruction(b)) {
+    return false;
+  }
+
+  // Both parameters are memory instructions.
+
+  // Both parameters only read from memory - it's OK to swap them.
+  if (!IsMemoryWriteInstruction(ir_context, a) &&
+      !IsMemoryWriteInstruction(ir_context, b)) {
+    return true;
+  }
+
+  // At least one of parameters is a memory read instruction.
+
+  // In theory, we can swap two memory instructions, one of which reads
+  // from the memory, if the read target (the pointer the memory is read from)
+  // and the write target (the memory is written into):
+  // - point to different memory regions
+  // - point to the same region with irrelevant value
+  // - point to the same region and the region is not used anymore.
+  //
+  // However, we can't currently determine if two pointers point to two
+  // different memory regions. That being said, if two pointers are not
+  // synonymous, they still might point to the same memory region. For example:
+  //   %1 = OpVariable ...
+  //   %2 = OpAccessChain %1 0
+  //   %3 = OpAccessChain %1 0
+  // In this pseudo-code, %2 and %3 are not synonymous but point to the same
+  // memory location. This implies that we can't determine if some memory
+  // location is not used in the block.
+  //
+  // With this in mind, consider two cases (we will build a table for each one):
+  // - one instruction only reads from memory, the other one only writes to it.
+  //   S - both point to the same memory region.
+  //   D - both point to different memory regions.
+  //   0, 1, 2 - neither, one of or both of the memory regions are irrelevant.
+  //   |-| - can't swap; |+| - can swap.
+  //     | 0 | 1 | 2 |
+  //   S : -   +   +
+  //   D : +   +   +
+  // - both instructions write to memory. Notation is the same.
+  //     | 0 | 1 | 2 |
+  //   S : *   +   +
+  //   D : +   +   +
+  //   * - we can swap two instructions that write into the same non-irrelevant
+  //   memory region if the written value is the same.
+  //
+  // Note that we can't always distinguish between S and D. Also note that
+  // in case of S, if one of the instructions is marked with
+  // PointeeValueIsIrrelevant, then the pointee of the other one is irrelevant
+  // as well even if the instruction is not marked with that fact.
+  //
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3723):
+  //  This procedure can be improved when we can determine if two pointers point
+  //  to different memory regions.
+
+  // From now on we will denote an instruction that:
+  // - only reads from memory - R
+  // - only writes into memory - W
+  // - reads and writes - RW
+  //
+  // Both |a| and |b| can be either W or RW at this point. Additionally, at most
+  // one of them can be R. The procedure below checks all possible combinations
+  // of R, W and RW according to the tables above. We conservatively assume that
+  // both |a| and |b| point to the same memory region.
+
+  auto memory_is_irrelevant = [ir_context, &fact_manager](uint32_t id) {
+    const auto* inst = ir_context->get_def_use_mgr()->GetDef(id);
+    if (!inst->type_id()) {
+      return false;
+    }
+
+    const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
+    assert(type && "|id| has invalid type");
+
+    if (!type->AsPointer()) {
+      return false;
+    }
+
+    return fact_manager.PointeeValueIsIrrelevant(id);
+  };
+
+  if (IsMemoryWriteInstruction(ir_context, a) &&
+      IsMemoryWriteInstruction(ir_context, b) &&
+      (memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) ||
+       memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b)))) {
+    // We ignore the case when the written value is the same. This is because
+    // the written value might not be equal to any of the instruction's
+    // operands.
+    return true;
+  }
+
+  if (IsMemoryReadInstruction(ir_context, a) &&
+      IsMemoryWriteInstruction(ir_context, b) &&
+      !memory_is_irrelevant(GetMemoryReadTarget(ir_context, a)) &&
+      !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b))) {
+    return false;
+  }
+
+  if (IsMemoryWriteInstruction(ir_context, a) &&
+      IsMemoryReadInstruction(ir_context, b) &&
+      !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) &&
+      !memory_is_irrelevant(GetMemoryReadTarget(ir_context, b))) {
+    return false;
+  }
+
+  return IsMemoryReadInstruction(ir_context, a) ||
+         IsMemoryReadInstruction(ir_context, b);
+}
+
+std::unordered_set<uint32_t> TransformationMoveInstructionDown::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_move_instruction_down.h b/source/fuzz/transformation_move_instruction_down.h
new file mode 100644
index 0000000..8585225
--- /dev/null
+++ b/source/fuzz/transformation_move_instruction_down.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_
+#define SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_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 TransformationMoveInstructionDown : public Transformation {
+ public:
+  explicit TransformationMoveInstructionDown(
+      const protobufs::TransformationMoveInstructionDown& message);
+
+  explicit TransformationMoveInstructionDown(
+      const protobufs::InstructionDescriptor& instruction);
+
+  // - |instruction| should be a descriptor of a valid instruction in the module
+  // - |instruction|'s opcode should be supported by this transformation
+  // - neither |instruction| nor its successor may be the last instruction in
+  //   the block
+  // - |instruction|'s successor may not be dependent on the |instruction|
+  // - it should be possible to insert |instruction|'s opcode after its
+  //   successor
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Swaps |instruction| with its successor.
+  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:
+  // Returns true if the |inst| is supported by this transformation.
+  static bool IsInstructionSupported(opt::IRContext* ir_context,
+                                     const opt::Instruction& inst);
+
+  // Returns true if |inst| represents a "simple" instruction. That is, it
+  // neither reads from nor writes to the memory and is not a barrier.
+  static bool IsSimpleInstruction(opt::IRContext* ir_context,
+                                  const opt::Instruction& inst);
+
+  // Returns true if |inst| reads from memory.
+  static bool IsMemoryReadInstruction(opt::IRContext* ir_context,
+                                      const opt::Instruction& inst);
+
+  // Returns id being used by |inst| to read from. |inst| must be a memory read
+  // instruction (see IsMemoryReadInstruction). Returned id is not guaranteed to
+  // have pointer type.
+  static uint32_t GetMemoryReadTarget(opt::IRContext* ir_context,
+                                      const opt::Instruction& inst);
+
+  // Returns true if |inst| that writes to the memory.
+  static bool IsMemoryWriteInstruction(opt::IRContext* ir_context,
+                                       const opt::Instruction& inst);
+
+  // Returns id being used by |inst| to write into. |inst| must be a memory
+  // write instruction (see IsMemoryWriteInstruction). Returned id is not
+  // guaranteed to have pointer type.
+  static uint32_t GetMemoryWriteTarget(opt::IRContext* ir_context,
+                                       const opt::Instruction& inst);
+
+  // Returns true if |inst| either reads from or writes to the memory
+  // (see IsMemoryReadInstruction and IsMemoryWriteInstruction accordingly).
+  static bool IsMemoryInstruction(opt::IRContext* ir_context,
+                                  const opt::Instruction& inst);
+
+  // Returns true if |inst| is a barrier instruction.
+  static bool IsBarrierInstruction(const opt::Instruction& inst);
+
+  // Returns true if it is possible to swap |a| and |b| without changing the
+  // module's semantics. |a| and |b| are required to be supported instructions
+  // (see IsInstructionSupported). In particular, if either |a| or |b| are
+  // memory or barrier instructions, some checks are used to only say that they
+  // can be swapped if the swap is definitely semantics-preserving.
+  static bool CanSafelySwapInstructions(opt::IRContext* ir_context,
+                                        const opt::Instruction& a,
+                                        const opt::Instruction& b,
+                                        const FactManager& fact_manager);
+
+  protobufs::TransformationMoveInstructionDown message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_MOVE_INSTRUCTION_DOWN_H_
diff --git a/source/fuzz/transformation_mutate_pointer.cpp b/source/fuzz/transformation_mutate_pointer.cpp
new file mode 100644
index 0000000..fefedbd
--- /dev/null
+++ b/source/fuzz/transformation_mutate_pointer.cpp
@@ -0,0 +1,171 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_mutate_pointer.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationMutatePointer::TransformationMutatePointer(
+    const protobufs::TransformationMutatePointer& message)
+    : message_(message) {}
+
+TransformationMutatePointer::TransformationMutatePointer(
+    uint32_t pointer_id, uint32_t fresh_id,
+    const protobufs::InstructionDescriptor& insert_before) {
+  message_.set_pointer_id(pointer_id);
+  message_.set_fresh_id(fresh_id);
+  *message_.mutable_insert_before() = insert_before;
+}
+
+bool TransformationMutatePointer::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Check that |fresh_id| is fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  auto* insert_before_inst =
+      FindInstruction(message_.insert_before(), ir_context);
+
+  // Check that |insert_before| is a valid instruction descriptor.
+  if (!insert_before_inst) {
+    return false;
+  }
+
+  // Check that it is possible to insert OpLoad and OpStore before
+  // |insert_before_inst|. We are only using OpLoad here since the result does
+  // not depend on the opcode.
+  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad,
+                                                    insert_before_inst)) {
+    return false;
+  }
+
+  const auto* pointer_inst =
+      ir_context->get_def_use_mgr()->GetDef(message_.pointer_id());
+
+  // Check that |pointer_id| is a result id of a valid pointer instruction.
+  if (!pointer_inst || !IsValidPointerInstruction(ir_context, *pointer_inst)) {
+    return false;
+  }
+
+  // Check that the module contains an irrelevant constant that will be used to
+  // mutate |pointer_inst|. The constant is irrelevant so that the latter
+  // transformation can change its value to something more interesting.
+  auto constant_id = fuzzerutil::MaybeGetZeroConstant(
+      ir_context, transformation_context,
+      fuzzerutil::GetPointeeTypeIdFromPointerType(ir_context,
+                                                  pointer_inst->type_id()),
+      true);
+  if (!constant_id) {
+    return false;
+  }
+
+  assert(fuzzerutil::IdIsAvailableBeforeInstruction(
+             ir_context, insert_before_inst, constant_id) &&
+         "Global constant instruction is not available before "
+         "|insert_before_inst|");
+
+  // Check that |pointer_inst| is available before |insert_before_inst|.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(
+      ir_context, insert_before_inst, pointer_inst->result_id());
+}
+
+void TransformationMutatePointer::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto* insert_before_inst =
+      FindInstruction(message_.insert_before(), ir_context);
+  assert(insert_before_inst && "|insert_before| descriptor is invalid");
+
+  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>(
+      ir_context, SpvOpLoad, pointee_type_id, message_.fresh_id(),
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}}));
+
+  // Insert a new value.
+  insert_before_inst->InsertBefore(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)}}}));
+
+  // Restore the original value.
+  insert_before_inst->InsertBefore(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()}}}));
+
+  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 {
+  protobufs::Transformation result;
+  *result.mutable_mutate_pointer() = message_;
+  return result;
+}
+
+bool TransformationMutatePointer::IsValidPointerInstruction(
+    opt::IRContext* ir_context, const opt::Instruction& inst) {
+  // |inst| must have both result id and type id and it may not cause undefined
+  // behaviour.
+  if (!inst.result_id() || !inst.type_id() || inst.opcode() == SpvOpUndef ||
+      inst.opcode() == SpvOpConstantNull) {
+    return false;
+  }
+
+  opt::Instruction* type_inst =
+      ir_context->get_def_use_mgr()->GetDef(inst.type_id());
+  assert(type_inst != nullptr && "|inst| has invalid type id");
+
+  // |inst| must be a pointer.
+  if (type_inst->opcode() != SpvOpTypePointer) {
+    return false;
+  }
+
+  // |inst| must have a supported storage class.
+  switch (static_cast<SpvStorageClass>(type_inst->GetSingleWordInOperand(0))) {
+    case SpvStorageClassFunction:
+    case SpvStorageClassPrivate:
+    case SpvStorageClassWorkgroup:
+      break;
+    default:
+      return false;
+  }
+
+  // |inst|'s pointee must consist of scalars and/or composites.
+  return fuzzerutil::CanCreateConstant(ir_context,
+                                       type_inst->GetSingleWordInOperand(1));
+}
+
+std::unordered_set<uint32_t> TransformationMutatePointer::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_mutate_pointer.h b/source/fuzz/transformation_mutate_pointer.h
new file mode 100644
index 0000000..b9f0965
--- /dev/null
+++ b/source/fuzz/transformation_mutate_pointer.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_MUTATE_POINTER_H_
+#define SOURCE_FUZZ_TRANSFORMATION_MUTATE_POINTER_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 TransformationMutatePointer : public Transformation {
+ public:
+  explicit TransformationMutatePointer(
+      const protobufs::TransformationMutatePointer& message);
+
+  explicit TransformationMutatePointer(
+      uint32_t pointer_id, uint32_t fresh_id,
+      const protobufs::InstructionDescriptor& insert_before);
+
+  // - |fresh_id| must be fresh.
+  // - |insert_before| must be a valid instruction descriptor of some
+  //   instruction in the module.
+  // - It should be possible to insert OpLoad and OpStore before
+  //   |insert_before|.
+  // - |pointer_id| must be a result id of some instruction in the module.
+  // - Instruction with result id |pointer_id| must be valid (see
+  //   IsValidPointerInstruction method).
+  // - There must exist an irrelevant constant in the module. Type of the
+  //   constant must be equal to the type of the |pointer_id|'s pointee.
+  // - |pointer_id| must be available (according to the dominance rules) before
+  //   |insert_before|.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Inserts the following instructions before |insert_before|:
+  //   %fresh_id = OpLoad %pointee_type_id %pointer_id
+  //               OpStore %pointer_id %constant_id
+  //               OpStore %pointer_id %fresh_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;
+
+  // Returns true if |inst| valid pointer according to the following:
+  // - |inst| has result id and type id.
+  // - |inst| is neither OpUndef nor OpConstantNull.
+  // - |inst| has a pointer type.
+  // - |inst|'s storage class is either Private, Function or Workgroup.
+  // - |inst|'s pointee type and all its constituents are either scalar or
+  //   composite.
+  static bool IsValidPointerInstruction(opt::IRContext* ir_context,
+                                        const opt::Instruction& inst);
+
+ private:
+  protobufs::TransformationMutatePointer message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_MUTATE_POINTER_H_
diff --git a/source/fuzz/transformation_outline_function.cpp b/source/fuzz/transformation_outline_function.cpp
index 05fd923..26f9e0b 100644
--- a/source/fuzz/transformation_outline_function.cpp
+++ b/source/fuzz/transformation_outline_function.cpp
@@ -21,20 +21,6 @@
 namespace spvtools {
 namespace fuzz {
 
-namespace {
-
-std::map<uint32_t, uint32_t> PairSequenceToMap(
-    const google::protobuf::RepeatedPtrField<protobufs::UInt32Pair>&
-        pair_sequence) {
-  std::map<uint32_t, uint32_t> result;
-  for (auto& pair : pair_sequence) {
-    result[pair.first()] = pair.second();
-  }
-  return result;
-}
-
-}  // namespace
-
 TransformationOutlineFunction::TransformationOutlineFunction(
     const spvtools::fuzz::protobufs::TransformationOutlineFunction& message)
     : message_(message) {}
@@ -44,8 +30,8 @@
     uint32_t new_function_struct_return_type_id, uint32_t new_function_type_id,
     uint32_t new_function_id, uint32_t new_function_region_entry_block,
     uint32_t new_caller_result_id, uint32_t new_callee_result_id,
-    std::map<uint32_t, uint32_t>&& input_id_to_fresh_id,
-    std::map<uint32_t, uint32_t>&& output_id_to_fresh_id) {
+    const std::map<uint32_t, uint32_t>& input_id_to_fresh_id,
+    const std::map<uint32_t, uint32_t>& output_id_to_fresh_id) {
   message_.set_entry_block(entry_block);
   message_.set_exit_block(exit_block);
   message_.set_new_function_struct_return_type_id(
@@ -55,22 +41,15 @@
   message_.set_new_function_region_entry_block(new_function_region_entry_block);
   message_.set_new_caller_result_id(new_caller_result_id);
   message_.set_new_callee_result_id(new_callee_result_id);
-  for (auto& entry : input_id_to_fresh_id) {
-    protobufs::UInt32Pair pair;
-    pair.set_first(entry.first);
-    pair.set_second(entry.second);
-    *message_.add_input_id_to_fresh_id() = pair;
-  }
-  for (auto& entry : output_id_to_fresh_id) {
-    protobufs::UInt32Pair pair;
-    pair.set_first(entry.first);
-    pair.set_second(entry.second);
-    *message_.add_output_id_to_fresh_id() = pair;
-  }
+  *message_.mutable_input_id_to_fresh_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(input_id_to_fresh_id);
+  *message_.mutable_output_id_to_fresh_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(output_id_to_fresh_id);
 }
 
 bool TransformationOutlineFunction::IsApplicable(
-    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
   std::set<uint32_t> ids_used_by_this_transformation;
 
   // The various new ids used by the transformation must be fresh and distinct.
@@ -252,12 +231,13 @@
 
   // For each region input id, i.e. every id defined outside the region but
   // used inside the region, ...
-  std::map<uint32_t, uint32_t> input_id_to_fresh_id_map =
-      PairSequenceToMap(message_.input_id_to_fresh_id());
+  auto input_id_to_fresh_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
   for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) {
     // There needs to be a corresponding fresh id to be used as a function
-    // parameter.
-    if (input_id_to_fresh_id_map.count(id) == 0) {
+    // parameter, or overflow ids need to be available.
+    if (input_id_to_fresh_id_map.count(id) == 0 &&
+        !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
       return false;
     }
     // Furthermore, if the input id has pointer type it must be an OpVariable
@@ -280,13 +260,15 @@
 
   // For each region output id -- i.e. every id defined inside the region but
   // used outside the region, ...
-  std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
-      PairSequenceToMap(message_.output_id_to_fresh_id());
+  auto output_id_to_fresh_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
   for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
     if (
         // ... there needs to be a corresponding fresh id that can hold the
-        // value for this id computed in the outlined function, and ...
-        output_id_to_fresh_id_map.count(id) == 0
+        // value for this id computed in the outlined function (or overflow ids
+        // must be available), and ...
+        (output_id_to_fresh_id_map.count(id) == 0 &&
+         !transformation_context.GetOverflowIdSource()->HasOverflowIds())
         // ... the output id must not have pointer type (to avoid creating a
         // struct with pointer members to pass data out of the outlined
         // function)
@@ -323,10 +305,27 @@
       GetRegionOutputIds(ir_context, region_blocks, original_region_exit_block);
 
   // Maps from input and output ids to fresh ids.
-  std::map<uint32_t, uint32_t> input_id_to_fresh_id_map =
-      PairSequenceToMap(message_.input_id_to_fresh_id());
-  std::map<uint32_t, uint32_t> output_id_to_fresh_id_map =
-      PairSequenceToMap(message_.output_id_to_fresh_id());
+  auto input_id_to_fresh_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
+  auto output_id_to_fresh_id_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
+
+  // Use overflow ids to augment these maps at any locations where fresh ids are
+  // required but not provided.
+  for (uint32_t id : region_input_ids) {
+    if (input_id_to_fresh_id_map.count(id) == 0) {
+      input_id_to_fresh_id_map.insert(
+          {id,
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+  }
+  for (uint32_t id : region_output_ids) {
+    if (output_id_to_fresh_id_map.count(id) == 0) {
+      output_id_to_fresh_id_map.insert(
+          {id,
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+  }
 
   UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
                                  output_id_to_fresh_id_map);
@@ -359,14 +358,6 @@
       region_input_ids, region_output_ids, input_id_to_fresh_id_map, ir_context,
       transformation_context);
 
-  // If the original function was livesafe, the new function should also be
-  // livesafe.
-  if (transformation_context->GetFactManager()->FunctionIsLivesafe(
-          original_region_entry_block->GetParent()->result_id())) {
-    transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
-        message_.new_function_id());
-  }
-
   // Adapt the region to be outlined so that its input ids are replaced with the
   // ids of the outlined function's input parameters, and so that output ids
   // are similarly remapped.
@@ -378,8 +369,8 @@
   // being outlined.
   PopulateOutlinedFunction(
       *original_region_entry_block, *original_region_exit_block, region_blocks,
-      region_output_ids, output_id_to_fresh_id_map, ir_context,
-      outlined_function.get(), transformation_context);
+      region_output_ids, output_id_to_type_id, output_id_to_fresh_id_map,
+      ir_context, outlined_function.get());
 
   // Collapse the region that has been outlined into a function down to a single
   // block that calls said function.
@@ -390,11 +381,28 @@
       std::move(cloned_exit_block_terminator), original_region_entry_block);
 
   // Add the outlined function to the module.
+  const auto* outlined_function_ptr = outlined_function.get();
   ir_context->module()->AddFunction(std::move(outlined_function));
 
   // Major surgery has been conducted on the module, so invalidate all analyses.
   ir_context->InvalidateAnalysesExceptFor(
       opt::IRContext::Analysis::kAnalysisNone);
+
+  // If the original function was livesafe, the new function should also be
+  // livesafe.
+  if (transformation_context->GetFactManager()->FunctionIsLivesafe(
+          original_region_entry_block->GetParent()->result_id())) {
+    transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
+        message_.new_function_id());
+  }
+
+  // Record the fact that all blocks in the outlined region are dead if the
+  // first block is dead.
+  if (transformation_context->GetFactManager()->BlockIsDead(
+          original_region_entry_block->id())) {
+    transformation_context->GetFactManager()->AddFactBlockIsDead(
+        outlined_function_ptr->entry()->id());
+  }
 }
 
 protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
@@ -452,7 +460,6 @@
           inst,
           [ir_context, &inst, region_exit_block, &region_set, &result](
               opt::Instruction* use, uint32_t /*unused*/) -> bool {
-
             // Find the block in which this id use occurs, recording the id as
             // an input id if the block is outside the region, with some
             // exceptions detailed below.
@@ -501,7 +508,6 @@
           &inst,
           [&region_set, ir_context, &inst, region_exit_block, &result](
               opt::Instruction* use, uint32_t /*unused*/) -> bool {
-
             // Find the block in which this id use occurs, recording the id as
             // an output id if the block is outside the region, with some
             // exceptions detailed below.
@@ -589,6 +595,12 @@
       for (uint32_t output_id : region_output_ids) {
         auto output_id_type =
             ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
+        if (ir_context->get_def_use_mgr()->GetDef(output_id_type)->opcode() ==
+            SpvOpTypeVoid) {
+          // We cannot add a void field to a struct.  We instead use OpUndef to
+          // handle void output ids.
+          continue;
+        }
         struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
       }
       // Add a new struct type to the module.
@@ -631,12 +643,22 @@
                 {function_type_id}}})));
 
   // Add one parameter to the function for each input id, using the fresh ids
-  // provided in |input_id_to_fresh_id_map|.
+  // provided in |input_id_to_fresh_id_map|, or overflow ids if needed.
   for (auto id : region_input_ids) {
+    uint32_t fresh_id = input_id_to_fresh_id_map.at(id);
     outlined_function->AddParameter(MakeUnique<opt::Instruction>(
         ir_context, SpvOpFunctionParameter,
-        ir_context->get_def_use_mgr()->GetDef(id)->type_id(),
-        input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList()));
+        ir_context->get_def_use_mgr()->GetDef(id)->type_id(), fresh_id,
+        opt::Instruction::OperandList()));
+
+    // Analyse the use of the new parameter instruction.
+    outlined_function->ForEachParam(
+        [fresh_id, ir_context](opt::Instruction* inst) {
+          if (inst->result_id() == fresh_id) {
+            ir_context->AnalyzeDefUse(inst);
+          }
+        });
+
     // If the input id is an irrelevant-valued variable, the same should be true
     // of the corresponding parameter.
     if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
@@ -742,9 +764,9 @@
     const opt::BasicBlock& original_region_exit_block,
     const std::set<opt::BasicBlock*>& region_blocks,
     const std::vector<uint32_t>& region_output_ids,
+    const std::map<uint32_t, uint32_t>& output_id_to_type_id,
     const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
-    opt::IRContext* ir_context, opt::Function* outlined_function,
-    TransformationContext* transformation_context) const {
+    opt::IRContext* ir_context, opt::Function* outlined_function) const {
   // When we create the exit block for the outlined region, we use this pointer
   // to track of it so that we can manipulate it later.
   opt::BasicBlock* outlined_region_exit_block = nullptr;
@@ -758,14 +780,6 @@
           opt::Instruction::OperandList()));
   outlined_region_entry_block->SetParent(outlined_function);
 
-  // If the original region's entry block was dead, the outlined region's entry
-  // block is also dead.
-  if (transformation_context->GetFactManager()->BlockIsDead(
-          original_region_entry_block.id())) {
-    transformation_context->GetFactManager()->AddFactBlockIsDead(
-        outlined_region_entry_block->id());
-  }
-
   if (&original_region_entry_block == &original_region_exit_block) {
     outlined_region_exit_block = outlined_region_entry_block.get();
   }
@@ -850,12 +864,16 @@
         ir_context, SpvOpReturn, 0, 0, opt::Instruction::OperandList()));
   } else {
     // In the case where there are output ids, we add an OpCompositeConstruct
-    // instruction to pack all the output values into a struct, and then an
-    // OpReturnValue instruction to return this struct.
+    // instruction to pack all the non-void output values into a struct, and
+    // then an OpReturnValue instruction to return this struct.
     opt::Instruction::OperandList struct_member_operands;
     for (uint32_t id : region_output_ids) {
-      struct_member_operands.push_back(
-          {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
+      if (ir_context->get_def_use_mgr()
+              ->GetDef(output_id_to_type_id.at(id))
+              ->opcode() != SpvOpTypeVoid) {
+        struct_member_operands.push_back(
+            {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
+      }
     }
     outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
         ir_context, SpvOpCompositeConstruct,
@@ -872,7 +890,7 @@
 }
 
 void TransformationOutlineFunction::ShrinkOriginalRegion(
-    opt::IRContext* ir_context, std::set<opt::BasicBlock*>& region_blocks,
+    opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_blocks,
     const std::vector<uint32_t>& region_input_ids,
     const std::vector<uint32_t>& region_output_ids,
     const std::map<uint32_t, uint32_t>& output_id_to_type_id,
@@ -941,15 +959,27 @@
 
   // If there are output ids, the function call will return a struct.  For each
   // output id, we add an extract operation to pull the appropriate struct
-  // member out into an output id.
-  for (uint32_t index = 0; index < region_output_ids.size(); ++index) {
-    uint32_t output_id = region_output_ids[index];
-    original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
-        ir_context, SpvOpCompositeExtract, output_id_to_type_id.at(output_id),
-        output_id,
-        opt::Instruction::OperandList(
-            {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
-             {SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}})));
+  // member out into an output id.  The exception is for output ids with void
+  // type.  There are no struct entries for these, so we use an OpUndef of void
+  // type instead.
+  uint32_t struct_member_index = 0;
+  for (uint32_t output_id : region_output_ids) {
+    uint32_t output_type_id = output_id_to_type_id.at(output_id);
+    if (ir_context->get_def_use_mgr()->GetDef(output_type_id)->opcode() ==
+        SpvOpTypeVoid) {
+      original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpUndef, output_type_id, output_id,
+          opt::Instruction::OperandList()));
+      // struct_member_index is not incremented since there was no struct member
+      // associated with this void-typed output id.
+    } else {
+      original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpCompositeExtract, output_type_id, output_id,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
+               {SPV_OPERAND_TYPE_LITERAL_INTEGER, {struct_member_index}}})));
+      struct_member_index++;
+    }
   }
 
   // Finally, we terminate the block with the merge instruction (if any) that
@@ -963,5 +993,23 @@
       std::move(cloned_exit_block_terminator));
 }
 
+std::unordered_set<uint32_t> TransformationOutlineFunction::GetFreshIds()
+    const {
+  std::unordered_set<uint32_t> result = {
+      message_.new_function_struct_return_type_id(),
+      message_.new_function_type_id(),
+      message_.new_function_id(),
+      message_.new_function_region_entry_block(),
+      message_.new_caller_result_id(),
+      message_.new_callee_result_id()};
+  for (auto& pair : message_.input_id_to_fresh_id()) {
+    result.insert(pair.second());
+  }
+  for (auto& pair : message_.output_id_to_fresh_id()) {
+    result.insert(pair.second());
+  }
+  return result;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_outline_function.h b/source/fuzz/transformation_outline_function.h
index ba439c8..36c0daf 100644
--- a/source/fuzz/transformation_outline_function.h
+++ b/source/fuzz/transformation_outline_function.h
@@ -38,8 +38,8 @@
       uint32_t new_function_type_id, uint32_t new_function_id,
       uint32_t new_function_region_entry_block, uint32_t new_caller_result_id,
       uint32_t new_callee_result_id,
-      std::map<uint32_t, uint32_t>&& input_id_to_fresh_id,
-      std::map<uint32_t, uint32_t>&& output_id_to_fresh_id);
+      const std::map<uint32_t, uint32_t>& input_id_to_fresh_id,
+      const std::map<uint32_t, uint32_t>& output_id_to_fresh_id);
 
   // - All the fresh ids occurring in the transformation must be distinct and
   //   fresh
@@ -73,7 +73,7 @@
   // - Unless the type required for the new function is already known,
   //   |message_.new_function_type_id| is used as the type id for a new function
   //   type, and the new function uses this type.
-  // - The new function starts with a dummy block with id
+  // - The new function starts with a placeholder block with id
   //   |message_.new_function_first_block|, which jumps straight to a successor
   //   block, to avoid violating rules on what the first block in a function may
   //   look like.
@@ -99,6 +99,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns the set of blocks dominated by |entry_block| and post-dominated
@@ -176,7 +178,8 @@
   // of |original_region_exit_block| so that it returns something appropriate,
   // and patching up branches to |original_region_entry_block| to refer to its
   // clone.  Parameters |region_output_ids| and |output_id_to_fresh_id_map| are
-  // used to determine what the function should return.
+  // used to determine what the function should return.  Parameter
+  // |output_id_to_type_id| provides the type of each output id.
   //
   // The |transformation_context| argument allow facts about blocks being
   // outlined, e.g. whether they are dead blocks, to be asserted about blocks
@@ -186,9 +189,9 @@
       const opt::BasicBlock& original_region_exit_block,
       const std::set<opt::BasicBlock*>& region_blocks,
       const std::vector<uint32_t>& region_output_ids,
+      const std::map<uint32_t, uint32_t>& output_id_to_type_id,
       const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
-      opt::IRContext* ir_context, opt::Function* outlined_function,
-      TransformationContext* transformation_context) const;
+      opt::IRContext* ir_context, opt::Function* outlined_function) const;
 
   // Shrinks the outlined region, given by |region_blocks|, down to the single
   // block |original_region_entry_block|.  This block is itself shrunk to just
@@ -207,7 +210,8 @@
   // function is called, this information cannot be gotten from the def-use
   // manager.
   void ShrinkOriginalRegion(
-      opt::IRContext* ir_context, std::set<opt::BasicBlock*>& region_blocks,
+      opt::IRContext* ir_context,
+      const std::set<opt::BasicBlock*>& region_blocks,
       const std::vector<uint32_t>& region_input_ids,
       const std::vector<uint32_t>& region_output_ids,
       const std::map<uint32_t, uint32_t>& output_id_to_type_id,
diff --git a/source/fuzz/transformation_permute_function_parameters.cpp b/source/fuzz/transformation_permute_function_parameters.cpp
index c4de743..a954cc1 100644
--- a/source/fuzz/transformation_permute_function_parameters.cpp
+++ b/source/fuzz/transformation_permute_function_parameters.cpp
@@ -161,5 +161,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationPermuteFunctionParameters::GetFreshIds() const {
+  return {message_.function_type_fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_permute_function_parameters.h b/source/fuzz/transformation_permute_function_parameters.h
index 8308051..38de8b1 100644
--- a/source/fuzz/transformation_permute_function_parameters.h
+++ b/source/fuzz/transformation_permute_function_parameters.h
@@ -51,6 +51,8 @@
   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:
diff --git a/source/fuzz/transformation_permute_phi_operands.cpp b/source/fuzz/transformation_permute_phi_operands.cpp
index 95e7a1f..ebd3c86 100644
--- a/source/fuzz/transformation_permute_phi_operands.cpp
+++ b/source/fuzz/transformation_permute_phi_operands.cpp
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/fuzz/transformation_permute_phi_operands.h"
+
 #include <vector>
 
 #include "source/fuzz/fuzzer_util.h"
-#include "source/fuzz/transformation_permute_phi_operands.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -90,5 +91,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationPermutePhiOperands::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_permute_phi_operands.h b/source/fuzz/transformation_permute_phi_operands.h
index df242e3..8198b70 100644
--- a/source/fuzz/transformation_permute_phi_operands.h
+++ b/source/fuzz/transformation_permute_phi_operands.h
@@ -44,6 +44,8 @@
   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:
diff --git a/source/fuzz/transformation_propagate_instruction_down.cpp b/source/fuzz/transformation_propagate_instruction_down.cpp
new file mode 100644
index 0000000..ba22e39
--- /dev/null
+++ b/source/fuzz/transformation_propagate_instruction_down.cpp
@@ -0,0 +1,592 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_propagate_instruction_down.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
+    const protobufs::TransformationPropagateInstructionDown& message)
+    : message_(message) {}
+
+TransformationPropagateInstructionDown::TransformationPropagateInstructionDown(
+    uint32_t block_id, uint32_t phi_fresh_id,
+    const std::map<uint32_t, uint32_t>& successor_id_to_fresh_id) {
+  message_.set_block_id(block_id);
+  message_.set_phi_fresh_id(phi_fresh_id);
+  *message_.mutable_successor_id_to_fresh_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(successor_id_to_fresh_id);
+}
+
+bool TransformationPropagateInstructionDown::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Check that we can apply this transformation to the |block_id|.
+  if (!IsApplicableToBlock(ir_context, message_.block_id())) {
+    return false;
+  }
+
+  const auto successor_id_to_fresh_id =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());
+
+  for (auto id : GetAcceptableSuccessors(ir_context, message_.block_id())) {
+    // Each successor must have a fresh id in the |successor_id_to_fresh_id|
+    // map, unless overflow ids are available.
+    if (!successor_id_to_fresh_id.count(id) &&
+        !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
+      return false;
+    }
+  }
+
+  std::vector<uint32_t> maybe_fresh_ids = {message_.phi_fresh_id()};
+  maybe_fresh_ids.reserve(successor_id_to_fresh_id.size());
+  for (const auto& entry : successor_id_to_fresh_id) {
+    maybe_fresh_ids.push_back(entry.second);
+  }
+
+  // All ids must be unique and fresh.
+  return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
+         std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
+                     [ir_context](uint32_t id) {
+                       return fuzzerutil::IsFreshId(ir_context, id);
+                     });
+}
+
+void TransformationPropagateInstructionDown::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // Get instruction to propagate down. There must be one.
+  auto* inst_to_propagate =
+      GetInstructionToPropagate(ir_context, message_.block_id());
+  assert(inst_to_propagate && "There must be an instruction to propagate");
+
+  auto successor_id_to_fresh_id =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id());
+  std::vector<uint32_t> created_inst_ids;
+  auto successor_ids = GetAcceptableSuccessors(ir_context, message_.block_id());
+
+  // Clone |inst_to_propagate| into every successor.
+  for (auto successor_id : successor_ids) {
+    std::unique_ptr<opt::Instruction> clone(
+        inst_to_propagate->Clone(ir_context));
+
+    uint32_t new_result_id;
+    if (successor_id_to_fresh_id.count(successor_id)) {
+      new_result_id = successor_id_to_fresh_id.at(successor_id);
+    } else {
+      assert(transformation_context->GetOverflowIdSource()->HasOverflowIds() &&
+             "Overflow ids must be available");
+      new_result_id =
+          transformation_context->GetOverflowIdSource()->GetNextOverflowId();
+      successor_id_to_fresh_id[successor_id] = new_result_id;
+    }
+
+    clone->SetResultId(new_result_id);
+    fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
+
+    auto* insert_before_inst = GetFirstInsertBeforeInstruction(
+        ir_context, successor_id, clone->opcode());
+    assert(insert_before_inst && "Can't insert into one of the successors");
+
+    insert_before_inst->InsertBefore(std::move(clone));
+    created_inst_ids.push_back(new_result_id);
+  }
+
+  // Add an OpPhi instruction into the module if possible.
+  if (auto merge_block_id = GetOpPhiBlockId(
+          ir_context, message_.block_id(), *inst_to_propagate, successor_ids)) {
+    opt::Instruction::OperandList in_operands;
+    std::unordered_set<uint32_t> visited_predecessors;
+    for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
+      if (visited_predecessors.count(predecessor_id)) {
+        // Merge block might have multiple identical predecessors.
+        continue;
+      }
+
+      visited_predecessors.insert(predecessor_id);
+
+      const auto* dominator_analysis = ir_context->GetDominatorAnalysis(
+          ir_context->cfg()->block(message_.block_id())->GetParent());
+
+      // Find the successor of |source_block| that dominates the predecessor of
+      // the merge block |predecessor_id|.
+      auto it = std::find_if(
+          successor_ids.begin(), successor_ids.end(),
+          [predecessor_id, dominator_analysis](uint32_t successor_id) {
+            return dominator_analysis->Dominates(successor_id, predecessor_id);
+          });
+
+      // OpPhi requires a single operand pair for every predecessor of the
+      // OpPhi's block.
+      assert(it != successor_ids.end() && "Unable to insert OpPhi");
+
+      in_operands.push_back(
+          {SPV_OPERAND_TYPE_ID, {successor_id_to_fresh_id.at(*it)}});
+      in_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
+    }
+
+    ir_context->cfg()
+        ->block(merge_block_id)
+        ->begin()
+        ->InsertBefore(MakeUnique<opt::Instruction>(
+            ir_context, SpvOpPhi, inst_to_propagate->type_id(),
+            message_.phi_fresh_id(), std::move(in_operands)));
+
+    fuzzerutil::UpdateModuleIdBound(ir_context, message_.phi_fresh_id());
+    created_inst_ids.push_back(message_.phi_fresh_id());
+  }
+
+  // Make sure analyses are updated when we adjust users of |inst_to_propagate|.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // Copy decorations from the original instructions to its propagated copies.
+  for (auto id : created_inst_ids) {
+    ir_context->get_decoration_mgr()->CloneDecorations(
+        inst_to_propagate->result_id(), id);
+  }
+
+  // Remove all decorations from the original instruction.
+  ir_context->get_decoration_mgr()->RemoveDecorationsFrom(
+      inst_to_propagate->result_id());
+
+  // Update every use of the |inst_to_propagate| with a result id of some of the
+  // newly created instructions.
+  ir_context->get_def_use_mgr()->ForEachUse(
+      inst_to_propagate, [ir_context, &created_inst_ids](
+                             opt::Instruction* user, uint32_t operand_index) {
+        assert(ir_context->get_instr_block(user) &&
+               "All decorations should have already been adjusted");
+
+        auto in_operand_index =
+            fuzzerutil::InOperandIndexFromOperandIndex(*user, operand_index);
+        for (auto id : created_inst_ids) {
+          if (fuzzerutil::IdIsAvailableAtUse(ir_context, user, in_operand_index,
+                                             id)) {
+            user->SetInOperand(in_operand_index, {id});
+            return;
+          }
+        }
+
+        // Every user of |inst_to_propagate| must be updated since we will
+        // remove that instruction from the module.
+        assert(false && "Every user of |inst_to_propagate| must be updated");
+      });
+
+  // Add synonyms about newly created instructions.
+  assert(inst_to_propagate->HasResultId() &&
+         "Result id is required to add facts");
+  if (transformation_context->GetFactManager()->IdIsIrrelevant(
+          inst_to_propagate->result_id())) {
+    for (auto id : created_inst_ids) {
+      transformation_context->GetFactManager()->AddFactIdIsIrrelevant(id);
+    }
+  } else {
+    std::vector<uint32_t> non_irrelevant_ids;
+    for (auto id : created_inst_ids) {
+      // |id| can be irrelevant implicitly (e.g. if we propagate it into a dead
+      // block).
+      if (!transformation_context->GetFactManager()->IdIsIrrelevant(id)) {
+        non_irrelevant_ids.push_back(id);
+      }
+    }
+
+    if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
+            inst_to_propagate->result_id())) {
+      for (auto id : non_irrelevant_ids) {
+        transformation_context->GetFactManager()
+            ->AddFactValueOfPointeeIsIrrelevant(id);
+      }
+    }
+
+    for (auto id : non_irrelevant_ids) {
+      transformation_context->GetFactManager()->AddFactDataSynonym(
+          MakeDataDescriptor(id, {}),
+          MakeDataDescriptor(non_irrelevant_ids[0], {}));
+    }
+  }
+
+  // Remove the propagated instruction from the module.
+  ir_context->KillInst(inst_to_propagate);
+
+  // We've adjusted all users - make sure these changes are analyzed.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationPropagateInstructionDown::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_propagate_instruction_down() = message_;
+  return result;
+}
+
+bool TransformationPropagateInstructionDown::IsOpcodeSupported(SpvOp opcode) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
+  //  We only support "simple" instructions that don't work with memory.
+  //  We should extend this so that we support the ones that modify the memory
+  //  too.
+  switch (opcode) {
+    case SpvOpUndef:
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpArrayLength:
+    case SpvOpVectorExtractDynamic:
+    case SpvOpVectorInsertDynamic:
+    case SpvOpVectorShuffle:
+    case SpvOpCompositeConstruct:
+    case SpvOpCompositeExtract:
+    case SpvOpCompositeInsert:
+    case SpvOpCopyObject:
+    case SpvOpTranspose:
+    case SpvOpConvertFToU:
+    case SpvOpConvertFToS:
+    case SpvOpConvertSToF:
+    case SpvOpConvertUToF:
+    case SpvOpUConvert:
+    case SpvOpSConvert:
+    case SpvOpFConvert:
+    case SpvOpQuantizeToF16:
+    case SpvOpSatConvertSToU:
+    case SpvOpSatConvertUToS:
+    case SpvOpBitcast:
+    case SpvOpSNegate:
+    case SpvOpFNegate:
+    case SpvOpIAdd:
+    case SpvOpFAdd:
+    case SpvOpISub:
+    case SpvOpFSub:
+    case SpvOpIMul:
+    case SpvOpFMul:
+    case SpvOpUDiv:
+    case SpvOpSDiv:
+    case SpvOpFDiv:
+    case SpvOpUMod:
+    case SpvOpSRem:
+    case SpvOpSMod:
+    case SpvOpFRem:
+    case SpvOpFMod:
+    case SpvOpVectorTimesScalar:
+    case SpvOpMatrixTimesScalar:
+    case SpvOpVectorTimesMatrix:
+    case SpvOpMatrixTimesVector:
+    case SpvOpMatrixTimesMatrix:
+    case SpvOpOuterProduct:
+    case SpvOpDot:
+    case SpvOpIAddCarry:
+    case SpvOpISubBorrow:
+    case SpvOpUMulExtended:
+    case SpvOpSMulExtended:
+    case SpvOpAny:
+    case SpvOpAll:
+    case SpvOpIsNan:
+    case SpvOpIsInf:
+    case SpvOpIsFinite:
+    case SpvOpIsNormal:
+    case SpvOpSignBitSet:
+    case SpvOpLessOrGreater:
+    case SpvOpOrdered:
+    case SpvOpUnordered:
+    case SpvOpLogicalEqual:
+    case SpvOpLogicalNotEqual:
+    case SpvOpLogicalOr:
+    case SpvOpLogicalAnd:
+    case SpvOpLogicalNot:
+    case SpvOpSelect:
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    case SpvOpUGreaterThan:
+    case SpvOpSGreaterThan:
+    case SpvOpUGreaterThanEqual:
+    case SpvOpSGreaterThanEqual:
+    case SpvOpULessThan:
+    case SpvOpSLessThan:
+    case SpvOpULessThanEqual:
+    case SpvOpSLessThanEqual:
+    case SpvOpFOrdEqual:
+    case SpvOpFUnordEqual:
+    case SpvOpFOrdNotEqual:
+    case SpvOpFUnordNotEqual:
+    case SpvOpFOrdLessThan:
+    case SpvOpFUnordLessThan:
+    case SpvOpFOrdGreaterThan:
+    case SpvOpFUnordGreaterThan:
+    case SpvOpFOrdLessThanEqual:
+    case SpvOpFUnordLessThanEqual:
+    case SpvOpFOrdGreaterThanEqual:
+    case SpvOpFUnordGreaterThanEqual:
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+    case SpvOpBitFieldInsert:
+    case SpvOpBitFieldSExtract:
+    case SpvOpBitFieldUExtract:
+    case SpvOpBitReverse:
+    case SpvOpBitCount:
+    case SpvOpCopyLogical:
+    case SpvOpPtrEqual:
+    case SpvOpPtrNotEqual:
+      return true;
+    default:
+      return false;
+  }
+}
+
+opt::Instruction*
+TransformationPropagateInstructionDown::GetInstructionToPropagate(
+    opt::IRContext* ir_context, uint32_t block_id) {
+  auto* block = ir_context->cfg()->block(block_id);
+  assert(block && "|block_id| is invalid");
+
+  for (auto it = block->rbegin(); it != block->rend(); ++it) {
+    if (!it->result_id() || !it->type_id() ||
+        !IsOpcodeSupported(it->opcode())) {
+      continue;
+    }
+
+    auto all_users_from_different_blocks =
+        ir_context->get_def_use_mgr()->WhileEachUser(
+            &*it, [ir_context, block](opt::Instruction* user) {
+              return ir_context->get_instr_block(user) != block;
+            });
+
+    if (!all_users_from_different_blocks) {
+      // We can't propagate an instruction if it's used in the same block.
+      continue;
+    }
+
+    return &*it;
+  }
+
+  return nullptr;
+}
+
+bool TransformationPropagateInstructionDown::IsApplicableToBlock(
+    opt::IRContext* ir_context, uint32_t block_id) {
+  // Check that |block_id| is valid.
+  const auto* block = fuzzerutil::MaybeFindBlock(ir_context, block_id);
+  if (!block) {
+    return false;
+  }
+
+  const auto* dominator_analysis =
+      ir_context->GetDominatorAnalysis(block->GetParent());
+
+  // |block| must be reachable.
+  if (!dominator_analysis->IsReachable(block)) {
+    return false;
+  }
+
+  // The block must have an instruction to propagate.
+  const auto* inst_to_propagate =
+      GetInstructionToPropagate(ir_context, block_id);
+  if (!inst_to_propagate) {
+    return false;
+  }
+
+  // Check that |block| has successors.
+  auto successor_ids = GetAcceptableSuccessors(ir_context, block_id);
+  if (successor_ids.empty()) {
+    return false;
+  }
+
+  // Check that |successor_block| doesn't have any OpPhi instructions that
+  // use |inst|.
+  for (auto successor_id : successor_ids) {
+    for (const auto& maybe_phi_inst : *ir_context->cfg()->block(successor_id)) {
+      if (maybe_phi_inst.opcode() != SpvOpPhi) {
+        // OpPhis can be intermixed with OpLine and OpNoLine.
+        continue;
+      }
+
+      for (uint32_t i = 0; i < maybe_phi_inst.NumInOperands(); i += 2) {
+        if (maybe_phi_inst.GetSingleWordInOperand(i) ==
+            inst_to_propagate->result_id()) {
+          return false;
+        }
+      }
+    }
+  }
+
+  // Get the result id of the block we will insert OpPhi instruction into.
+  // This is either 0 or a result id of some merge block in the function.
+  auto phi_block_id =
+      GetOpPhiBlockId(ir_context, block_id, *inst_to_propagate, successor_ids);
+
+  // Make sure we can adjust all users of the propagated instruction.
+  return ir_context->get_def_use_mgr()->WhileEachUse(
+      inst_to_propagate,
+      [ir_context, &successor_ids, dominator_analysis, phi_block_id](
+          opt::Instruction* user, uint32_t index) {
+        const auto* user_block = ir_context->get_instr_block(user);
+
+        if (!user_block) {
+          // |user| might be a global instruction (e.g. OpDecorate).
+          return true;
+        }
+
+        // Check that at least one of the ids in |successor_ids| or a
+        // |phi_block_id| dominates |user|'s block (or its predecessor if the
+        // user is an OpPhi). We can't use fuzzerutil::IdIsAvailableAtUse since
+        // the id in question hasn't yet been created in the module.
+        auto block_id_to_dominate = user->opcode() == SpvOpPhi
+                                        ? user->GetSingleWordOperand(index + 1)
+                                        : user_block->id();
+
+        if (phi_block_id != 0 &&
+            dominator_analysis->Dominates(phi_block_id, block_id_to_dominate)) {
+          return true;
+        }
+
+        return std::any_of(
+            successor_ids.begin(), successor_ids.end(),
+            [dominator_analysis, block_id_to_dominate](uint32_t id) {
+              return dominator_analysis->Dominates(id, block_id_to_dominate);
+            });
+      });
+}
+
+opt::Instruction*
+TransformationPropagateInstructionDown::GetFirstInsertBeforeInstruction(
+    opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode) {
+  auto* block = ir_context->cfg()->block(block_id);
+
+  auto it = block->begin();
+
+  while (it != block->end() &&
+         !fuzzerutil::CanInsertOpcodeBeforeInstruction(opcode, it)) {
+    ++it;
+  }
+
+  return it == block->end() ? nullptr : &*it;
+}
+
+std::unordered_set<uint32_t>
+TransformationPropagateInstructionDown::GetAcceptableSuccessors(
+    opt::IRContext* ir_context, uint32_t block_id) {
+  const auto* block = ir_context->cfg()->block(block_id);
+  assert(block && "|block_id| is invalid");
+
+  const auto* inst = GetInstructionToPropagate(ir_context, block_id);
+  assert(inst && "The block must have an instruction to propagate");
+
+  std::unordered_set<uint32_t> result;
+  block->ForEachSuccessorLabel([ir_context, &result,
+                                inst](uint32_t successor_id) {
+    if (result.count(successor_id)) {
+      return;
+    }
+
+    auto* successor_block = ir_context->cfg()->block(successor_id);
+
+    // We can't propagate |inst| into |successor_block| if the latter is not
+    // dominated by the |inst|'s dependencies.
+    if (!inst->WhileEachInId([ir_context, successor_block](const uint32_t* id) {
+          return fuzzerutil::IdIsAvailableBeforeInstruction(
+              ir_context, &*successor_block->begin(), *id);
+        })) {
+      return;
+    }
+
+    // We don't propagate any "special" instructions (e.g. OpSelectionMerge
+    // etc), thus, insertion point must always exist if the module is valid.
+    assert(GetFirstInsertBeforeInstruction(ir_context, successor_id,
+                                           inst->opcode()) &&
+           "There must exist an insertion point.");
+
+    result.insert(successor_id);
+  });
+
+  return result;
+}
+
+uint32_t TransformationPropagateInstructionDown::GetOpPhiBlockId(
+    opt::IRContext* ir_context, uint32_t block_id,
+    const opt::Instruction& inst_to_propagate,
+    const std::unordered_set<uint32_t>& successor_ids) {
+  const auto* block = ir_context->cfg()->block(block_id);
+
+  // |block_id| must belong to some construct.
+  auto merge_block_id =
+      block->GetMergeInst()
+          ? block->GetMergeInst()->GetSingleWordInOperand(0)
+          : ir_context->GetStructuredCFGAnalysis()->MergeBlock(block_id);
+  if (!merge_block_id) {
+    return 0;
+  }
+
+  const auto* dominator_analysis =
+      ir_context->GetDominatorAnalysis(block->GetParent());
+
+  // Check that |merge_block_id| is reachable in the CFG and |block_id|
+  // dominates |merge_block_id|.
+  if (!dominator_analysis->IsReachable(merge_block_id) ||
+      !dominator_analysis->Dominates(block_id, merge_block_id)) {
+    return 0;
+  }
+
+  // We can't insert an OpPhi into |merge_block_id| if it's an acceptable
+  // successor of |block_id|.
+  if (successor_ids.count(merge_block_id)) {
+    return 0;
+  }
+
+  // All predecessors of the merge block must be dominated by at least one
+  // successor of the |block_id|.
+  assert(!ir_context->cfg()->preds(merge_block_id).empty() &&
+         "Merge block must be reachable");
+  for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) {
+    if (std::none_of(
+            successor_ids.begin(), successor_ids.end(),
+            [dominator_analysis, predecessor_id](uint32_t successor_id) {
+              return dominator_analysis->Dominates(successor_id,
+                                                   predecessor_id);
+            })) {
+      return 0;
+    }
+  }
+
+  const auto* propagate_type =
+      ir_context->get_type_mgr()->GetType(inst_to_propagate.type_id());
+  assert(propagate_type && "|inst_to_propagate| must have a valid type");
+
+  // VariablePointers capability implicitly declares
+  // VariablePointersStorageBuffer. We need those capabilities since otherwise
+  // OpPhi instructions cannot have operands of pointer types.
+  if (propagate_type->AsPointer() &&
+      !ir_context->get_feature_mgr()->HasCapability(
+          SpvCapabilityVariablePointersStorageBuffer)) {
+    return 0;
+  }
+
+  return merge_block_id;
+}
+
+std::unordered_set<uint32_t>
+TransformationPropagateInstructionDown::GetFreshIds() const {
+  std::unordered_set<uint32_t> result = {message_.phi_fresh_id()};
+  for (const auto& pair : message_.successor_id_to_fresh_id()) {
+    result.insert(pair.second());
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_propagate_instruction_down.h b/source/fuzz/transformation_propagate_instruction_down.h
new file mode 100644
index 0000000..7eca1ad
--- /dev/null
+++ b/source/fuzz/transformation_propagate_instruction_down.h
@@ -0,0 +1,184 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_PROPAGATE_INSTRUCTION_DOWN_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_DOWN_H_
+
+#include <map>
+
+#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 TransformationPropagateInstructionDown : public Transformation {
+ public:
+  explicit TransformationPropagateInstructionDown(
+      const protobufs::TransformationPropagateInstructionDown& message);
+
+  TransformationPropagateInstructionDown(
+      uint32_t block_id, uint32_t phi_fresh_id,
+      const std::map<uint32_t, uint32_t>& successor_id_to_fresh_id);
+
+  // - It should be possible to apply this transformation to |block_id| (see
+  //   IsApplicableToBlock method).
+  // - Every acceptable successor of |block_id| (see GetAcceptableSuccessors
+  //   method) must have an entry in the |successor_id_to_fresh_id| map unless
+  //   overflow ids are available.
+  // - All values in |successor_id_to_fresh_id| and |phi_fresh_id| must be
+  //   unique and fresh.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // - Adds a clone of the propagated instruction into every acceptable
+  //   successor of |block_id|.
+  // - Removes the original instruction.
+  // - Creates an OpPhi instruction if possible, that tries to group created
+  //   clones.
+  // - If the original instruction's id was irrelevant - marks created
+  //   instructions as irrelevant. Otherwise, marks the created instructions as
+  //   synonymous to each other if possible (i.e. skips instructions, copied
+  //   into dead blocks).
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if this transformation can be applied to the block with id
+  // |block_id|. Concretely, returns true iff:
+  // - |block_id| is a result id of some reachable basic block in the module.
+  // - the block has an instruction to propagate (see
+  //   GetInstructionToPropagate method).
+  // - the block has at least one acceptable successor (see
+  //   GetAcceptableSuccessors method).
+  // - none of the acceptable successors have OpPhi instructions that use the
+  //   original instruction.
+  // - it is possible to replace every use of the original instruction with some
+  //   of the propagated instructions (or an OpPhi if we can create it - see
+  //   GetOpPhiBlockId method).
+  static bool IsApplicableToBlock(opt::IRContext* ir_context,
+                                  uint32_t block_id);
+
+  // Returns ids of successors of |block_id|, that can be used to propagate an
+  // instruction into. Concretely, a successor block is acceptable if all
+  // dependencies of the propagated instruction dominate it. Note that this
+  // implies that an acceptable successor must be reachable in the CFG.
+  // For example:
+  //    %1 = OpLabel
+  //         OpSelectionMerge %2 None
+  //         OpBranchConditional %cond %2 %3
+  //    %3 = OpLabel
+  //    %4 = OpUndef %int
+  //    %5 = OpCopyObject %int %4
+  //         OpBranch %2
+  //    %2 = OpLabel
+  //    ...
+  // In this example, %2 is not an acceptable successor of %3 since one of the
+  // dependencies (%4) of the propagated instruction (%5) does not dominate it.
+  static std::unordered_set<uint32_t> GetAcceptableSuccessors(
+      opt::IRContext* ir_context, uint32_t block_id);
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+ private:
+  // Returns the last possible instruction in the |block_id| that satisfies the
+  // following properties:
+  // - has result id
+  // - has type id
+  // - has supported opcode (see IsOpcodeSupported method)
+  // - has no users in its basic block.
+  // Returns nullptr if no such an instruction exists. For example:
+  //    %1 = OpLabel
+  //    %2 = OpUndef %int
+  //    %3 = OpUndef %int
+  //         OpStore %var %3
+  //         OpBranch %some_block
+  // In this example:
+  // - We cannot propagate OpBranch nor OpStore since they both have unsupported
+  //   opcodes and have neither result ids nor type ids.
+  // - We cannot propagate %3 either since it is used by OpStore.
+  // - We can propagate %2 since it satisfies all our conditions.
+  // The basic idea behind this method it to make sure that the returned
+  // instruction will not break domination rules in its original block when
+  // propagated.
+  static opt::Instruction* GetInstructionToPropagate(opt::IRContext* ir_context,
+                                                     uint32_t block_id);
+
+  // Returns true if |opcode| is supported by this transformation.
+  static bool IsOpcodeSupported(SpvOp opcode);
+
+  // Returns the first instruction in the |block| that allows us to insert
+  // |opcode| above itself. Returns nullptr is no such instruction exists.
+  static opt::Instruction* GetFirstInsertBeforeInstruction(
+      opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode);
+
+  // Returns a result id of a basic block, where an OpPhi instruction can be
+  // inserted. Returns nullptr if it's not possible to create an OpPhi. The
+  // created OpPhi instruction groups all the propagated clones of the original
+  // instruction. |block_id| is a result id of the block we propagate the
+  // instruction from. |successor_ids| contains result ids of the successors we
+  // propagate the instruction into. Concretely, returns a non-null value if:
+  // - |block_id| is in some construct.
+  // - The merge block of that construct is reachable.
+  // - |block_id| dominates that merge block.
+  // - That merge block may not be an acceptable successor of |block_id|.
+  // - There must be at least one |block_id|'s acceptable successor for every
+  //   predecessor of the merge block, dominating that predecessor.
+  // - We can't create an OpPhi if the module has neither VariablePointers nor
+  //   VariablePointersStorageBuffer capabilities.
+  // A simple example of when we can insert an OpPhi instruction is:
+  // - This snippet of code:
+  //    %1 = OpLabel
+  //    %2 = OpUndef %int
+  //         OpSelectionMerge %5 None
+  //         OpBranchConditional %cond %3 %4
+  //    %3 = OpLabel
+  //         OpBranch %5
+  //    %4 = OpLabel
+  //         OpBranch %5
+  //    %5 = OpLabel
+  //         ...
+  //   will be transformed into the following one (if %2 is propagated):
+  //    %1 = OpLabel
+  //         OpSelectionMerge %5 None
+  //         OpBranchConditional %cond %3 %4
+  //    %3 = OpLabel
+  //    %6 = OpUndef %int
+  //         OpBranch %5
+  //    %4 = OpLabel
+  //    %7 = OpUndef %int
+  //         OpBranch %5
+  //    %5 = OpLabel
+  //    %8 = OpPhi %int %6 %3 %7 %4
+  //         ...
+  // The fact that we introduce an OpPhi allows us to increase the applicability
+  // of the transformation. Concretely, we wouldn't be able to apply it in the
+  // example above if %2 were used in %5. Some more complicated examples can be
+  // found in unit tests.
+  static uint32_t GetOpPhiBlockId(
+      opt::IRContext* ir_context, uint32_t block_id,
+      const opt::Instruction& inst_to_propagate,
+      const std::unordered_set<uint32_t>& successor_ids);
+
+  protobufs::TransformationPropagateInstructionDown message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_DOWN_H_
diff --git a/source/fuzz/transformation_propagate_instruction_up.cpp b/source/fuzz/transformation_propagate_instruction_up.cpp
new file mode 100644
index 0000000..a2cacf4
--- /dev/null
+++ b/source/fuzz/transformation_propagate_instruction_up.cpp
@@ -0,0 +1,417 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_propagate_instruction_up.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+uint32_t GetResultIdFromLabelId(const opt::Instruction& phi_inst,
+                                uint32_t label_id) {
+  assert(phi_inst.opcode() == SpvOpPhi && "|phi_inst| is not an OpPhi");
+
+  for (uint32_t i = 1; i < phi_inst.NumInOperands(); i += 2) {
+    if (phi_inst.GetSingleWordInOperand(i) == label_id) {
+      return phi_inst.GetSingleWordInOperand(i - 1);
+    }
+  }
+
+  return 0;
+}
+
+bool ContainsPointers(const opt::analysis::Type& type) {
+  switch (type.kind()) {
+    case opt::analysis::Type::kPointer:
+      return true;
+    case opt::analysis::Type::kStruct:
+      return std::any_of(type.AsStruct()->element_types().begin(),
+                         type.AsStruct()->element_types().end(),
+                         [](const opt::analysis::Type* element_type) {
+                           return ContainsPointers(*element_type);
+                         });
+    default:
+      return false;
+  }
+}
+
+bool HasValidDependencies(opt::IRContext* ir_context, opt::Instruction* inst) {
+  const auto* inst_block = ir_context->get_instr_block(inst);
+  assert(inst_block &&
+         "This function shouldn't be applied to global instructions or function"
+         "parameters");
+
+  for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+    const auto& operand = inst->GetInOperand(i);
+    if (operand.type != SPV_OPERAND_TYPE_ID) {
+      // Consider only <id> operands.
+      continue;
+    }
+
+    auto* dependency = ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
+    assert(dependency && "Operand has invalid id");
+
+    if (ir_context->get_instr_block(dependency) == inst_block &&
+        dependency->opcode() != SpvOpPhi) {
+      // |dependency| is "valid" if it's an OpPhi from the same basic block or
+      // an instruction from a different basic block.
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace
+
+TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
+    const protobufs::TransformationPropagateInstructionUp& message)
+    : message_(message) {}
+
+TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
+    uint32_t block_id,
+    const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id) {
+  message_.set_block_id(block_id);
+  *message_.mutable_predecessor_id_to_fresh_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(predecessor_id_to_fresh_id);
+}
+
+bool TransformationPropagateInstructionUp::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // Check that we can apply this transformation to the |block_id|.
+  if (!IsApplicableToBlock(ir_context, message_.block_id())) {
+    return false;
+  }
+
+  const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
+      message_.predecessor_id_to_fresh_id());
+  for (auto id : ir_context->cfg()->preds(message_.block_id())) {
+    // Each predecessor must have a fresh id in the |predecessor_id_to_fresh_id|
+    // map.
+    if (!predecessor_id_to_fresh_id.count(id)) {
+      return false;
+    }
+  }
+
+  std::vector<uint32_t> maybe_fresh_ids;
+  maybe_fresh_ids.reserve(predecessor_id_to_fresh_id.size());
+  for (const auto& entry : predecessor_id_to_fresh_id) {
+    maybe_fresh_ids.push_back(entry.second);
+  }
+
+  // All ids must be unique and fresh.
+  return !fuzzerutil::HasDuplicates(maybe_fresh_ids) &&
+         std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(),
+                     [ir_context](uint32_t id) {
+                       return fuzzerutil::IsFreshId(ir_context, id);
+                     });
+}
+
+void TransformationPropagateInstructionUp::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto* inst = GetInstructionToPropagate(ir_context, message_.block_id());
+  assert(inst &&
+         "The block must have at least one supported instruction to propagate");
+  assert(inst->result_id() && inst->type_id() &&
+         "|inst| must have a result id and a type id");
+
+  opt::Instruction::OperandList op_phi_operands;
+  const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
+      message_.predecessor_id_to_fresh_id());
+  std::unordered_set<uint32_t> visited_predecessors;
+  for (auto predecessor_id : ir_context->cfg()->preds(message_.block_id())) {
+    // A block can have multiple identical predecessors.
+    if (visited_predecessors.count(predecessor_id)) {
+      continue;
+    }
+
+    visited_predecessors.insert(predecessor_id);
+
+    auto new_result_id = predecessor_id_to_fresh_id.at(predecessor_id);
+
+    // Compute InOperands for the OpPhi instruction to be inserted later.
+    op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {new_result_id}});
+    op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
+
+    // Create a clone of the |inst| to be inserted into the |predecessor_id|.
+    std::unique_ptr<opt::Instruction> clone(inst->Clone(ir_context));
+    clone->SetResultId(new_result_id);
+
+    fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
+
+    // Adjust |clone|'s operands to account for possible dependencies on OpPhi
+    // instructions from the same basic block.
+    for (uint32_t i = 0; i < clone->NumInOperands(); ++i) {
+      auto& operand = clone->GetInOperand(i);
+      if (operand.type != SPV_OPERAND_TYPE_ID) {
+        // Consider only ids.
+        continue;
+      }
+
+      const auto* dependency_inst =
+          ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
+      assert(dependency_inst && "|clone| depends on an invalid id");
+
+      if (ir_context->get_instr_block(dependency_inst->result_id()) !=
+          ir_context->cfg()->block(message_.block_id())) {
+        // We don't need to adjust anything if |dependency_inst| is from a
+        // different block, a global instruction or a function parameter.
+        continue;
+      }
+
+      assert(dependency_inst->opcode() == SpvOpPhi &&
+             "Propagated instruction can depend only on OpPhis from the same "
+             "basic block or instructions from different basic blocks");
+
+      auto new_id = GetResultIdFromLabelId(*dependency_inst, predecessor_id);
+      assert(new_id && "OpPhi instruction is missing a predecessor");
+      operand.words[0] = new_id;
+    }
+
+    auto* insert_before_inst = fuzzerutil::GetLastInsertBeforeInstruction(
+        ir_context, predecessor_id, clone->opcode());
+    assert(insert_before_inst && "Can't insert |clone| into |predecessor_id");
+
+    insert_before_inst->InsertBefore(std::move(clone));
+  }
+
+  // Insert an OpPhi instruction into the basic block of |inst|.
+  ir_context->get_instr_block(inst)->begin()->InsertBefore(
+      MakeUnique<opt::Instruction>(ir_context, SpvOpPhi, inst->type_id(),
+                                   inst->result_id(),
+                                   std::move(op_phi_operands)));
+
+  // Remove |inst| from the basic block.
+  ir_context->KillInst(inst);
+
+  // We have changed the module so most analyzes are now invalid.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationPropagateInstructionUp::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_propagate_instruction_up() = message_;
+  return result;
+}
+
+bool TransformationPropagateInstructionUp::IsOpcodeSupported(SpvOp opcode) {
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
+  //  We only support "simple" instructions that don't work with memory.
+  //  We should extend this so that we support the ones that modify the memory
+  //  too.
+  switch (opcode) {
+    case SpvOpUndef:
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpArrayLength:
+    case SpvOpVectorExtractDynamic:
+    case SpvOpVectorInsertDynamic:
+    case SpvOpVectorShuffle:
+    case SpvOpCompositeConstruct:
+    case SpvOpCompositeExtract:
+    case SpvOpCompositeInsert:
+    case SpvOpCopyObject:
+    case SpvOpTranspose:
+    case SpvOpConvertFToU:
+    case SpvOpConvertFToS:
+    case SpvOpConvertSToF:
+    case SpvOpConvertUToF:
+    case SpvOpUConvert:
+    case SpvOpSConvert:
+    case SpvOpFConvert:
+    case SpvOpQuantizeToF16:
+    case SpvOpSatConvertSToU:
+    case SpvOpSatConvertUToS:
+    case SpvOpBitcast:
+    case SpvOpSNegate:
+    case SpvOpFNegate:
+    case SpvOpIAdd:
+    case SpvOpFAdd:
+    case SpvOpISub:
+    case SpvOpFSub:
+    case SpvOpIMul:
+    case SpvOpFMul:
+    case SpvOpUDiv:
+    case SpvOpSDiv:
+    case SpvOpFDiv:
+    case SpvOpUMod:
+    case SpvOpSRem:
+    case SpvOpSMod:
+    case SpvOpFRem:
+    case SpvOpFMod:
+    case SpvOpVectorTimesScalar:
+    case SpvOpMatrixTimesScalar:
+    case SpvOpVectorTimesMatrix:
+    case SpvOpMatrixTimesVector:
+    case SpvOpMatrixTimesMatrix:
+    case SpvOpOuterProduct:
+    case SpvOpDot:
+    case SpvOpIAddCarry:
+    case SpvOpISubBorrow:
+    case SpvOpUMulExtended:
+    case SpvOpSMulExtended:
+    case SpvOpAny:
+    case SpvOpAll:
+    case SpvOpIsNan:
+    case SpvOpIsInf:
+    case SpvOpIsFinite:
+    case SpvOpIsNormal:
+    case SpvOpSignBitSet:
+    case SpvOpLessOrGreater:
+    case SpvOpOrdered:
+    case SpvOpUnordered:
+    case SpvOpLogicalEqual:
+    case SpvOpLogicalNotEqual:
+    case SpvOpLogicalOr:
+    case SpvOpLogicalAnd:
+    case SpvOpLogicalNot:
+    case SpvOpSelect:
+    case SpvOpIEqual:
+    case SpvOpINotEqual:
+    case SpvOpUGreaterThan:
+    case SpvOpSGreaterThan:
+    case SpvOpUGreaterThanEqual:
+    case SpvOpSGreaterThanEqual:
+    case SpvOpULessThan:
+    case SpvOpSLessThan:
+    case SpvOpULessThanEqual:
+    case SpvOpSLessThanEqual:
+    case SpvOpFOrdEqual:
+    case SpvOpFUnordEqual:
+    case SpvOpFOrdNotEqual:
+    case SpvOpFUnordNotEqual:
+    case SpvOpFOrdLessThan:
+    case SpvOpFUnordLessThan:
+    case SpvOpFOrdGreaterThan:
+    case SpvOpFUnordGreaterThan:
+    case SpvOpFOrdLessThanEqual:
+    case SpvOpFUnordLessThanEqual:
+    case SpvOpFOrdGreaterThanEqual:
+    case SpvOpFUnordGreaterThanEqual:
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+    case SpvOpBitFieldInsert:
+    case SpvOpBitFieldSExtract:
+    case SpvOpBitFieldUExtract:
+    case SpvOpBitReverse:
+    case SpvOpBitCount:
+    case SpvOpCopyLogical:
+    case SpvOpPtrEqual:
+    case SpvOpPtrNotEqual:
+      return true;
+    default:
+      return false;
+  }
+}
+
+opt::Instruction*
+TransformationPropagateInstructionUp::GetInstructionToPropagate(
+    opt::IRContext* ir_context, uint32_t block_id) {
+  auto* block = ir_context->cfg()->block(block_id);
+  assert(block && "|block_id| is invalid");
+
+  for (auto& inst : *block) {
+    // We look for the first instruction in the block that satisfies the
+    // following rules:
+    // - it's not an OpPhi
+    // - it must be supported by this transformation
+    // - it may depend only on instructions from different basic blocks or on
+    //   OpPhi instructions from the same basic block.
+    if (inst.opcode() == SpvOpPhi || !IsOpcodeSupported(inst.opcode()) ||
+        !inst.type_id() || !inst.result_id()) {
+      continue;
+    }
+
+    const auto* inst_type = ir_context->get_type_mgr()->GetType(inst.type_id());
+    assert(inst_type && "|inst| has invalid type");
+
+    if (inst_type->AsSampledImage()) {
+      // OpTypeSampledImage cannot be used as an argument to OpPhi instructions,
+      // thus we cannot support this type.
+      continue;
+    }
+
+    if (!ir_context->get_feature_mgr()->HasCapability(
+            SpvCapabilityVariablePointersStorageBuffer) &&
+        ContainsPointers(*inst_type)) {
+      // OpPhi supports pointer operands only with VariablePointers or
+      // VariablePointersStorageBuffer capabilities.
+      //
+      // Note that VariablePointers capability implicitly declares
+      // VariablePointersStorageBuffer capability.
+      continue;
+    }
+
+    if (!HasValidDependencies(ir_context, &inst)) {
+      continue;
+    }
+
+    return &inst;
+  }
+
+  return nullptr;
+}
+
+bool TransformationPropagateInstructionUp::IsApplicableToBlock(
+    opt::IRContext* ir_context, uint32_t block_id) {
+  // Check that |block_id| is valid.
+  const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id);
+  if (!label_inst || label_inst->opcode() != SpvOpLabel) {
+    return false;
+  }
+
+  // Check that |block| has predecessors.
+  const auto& predecessors = ir_context->cfg()->preds(block_id);
+  if (predecessors.empty()) {
+    return false;
+  }
+
+  // The block must contain an instruction to propagate.
+  const auto* inst_to_propagate =
+      GetInstructionToPropagate(ir_context, block_id);
+  if (!inst_to_propagate) {
+    return false;
+  }
+
+  // We should be able to insert |inst_to_propagate| into every predecessor of
+  // |block|.
+  return std::all_of(predecessors.begin(), predecessors.end(),
+                     [ir_context, inst_to_propagate](uint32_t predecessor_id) {
+                       return fuzzerutil::GetLastInsertBeforeInstruction(
+                                  ir_context, predecessor_id,
+                                  inst_to_propagate->opcode()) != nullptr;
+                     });
+}
+
+std::unordered_set<uint32_t> TransformationPropagateInstructionUp::GetFreshIds()
+    const {
+  std::unordered_set<uint32_t> result;
+  for (auto& pair : message_.predecessor_id_to_fresh_id()) {
+    result.insert(pair.second());
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_propagate_instruction_up.h b/source/fuzz/transformation_propagate_instruction_up.h
new file mode 100644
index 0000000..6354094
--- /dev/null
+++ b/source/fuzz/transformation_propagate_instruction_up.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_PROPAGATE_INSTRUCTION_UP_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_
+
+#include <map>
+
+#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 TransformationPropagateInstructionUp : public Transformation {
+ public:
+  explicit TransformationPropagateInstructionUp(
+      const protobufs::TransformationPropagateInstructionUp& message);
+
+  TransformationPropagateInstructionUp(
+      uint32_t block_id,
+      const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id);
+
+  // - |block_id| must be a valid result id of some OpLabel instruction.
+  // - |block_id| must have at least one predecessor
+  // - |block_id| must contain an instruction that can be propagated using this
+  //   transformation
+  // - the instruction can be propagated if:
+  //   - it's not an OpPhi
+  //   - it is supported by this transformation
+  //   - it depends only on instructions from different basic blocks or on
+  //     OpPhi instructions from the same basic block
+  // - it should be possible to insert the propagated instruction at the end of
+  //   each |block_id|'s predecessor
+  // - |predecessor_id_to_fresh_id| must have an entry for at least every
+  //   predecessor of |block_id|
+  // - each value in the |predecessor_id_to_fresh_id| map must be a fresh id
+  // - all fresh ids in the |predecessor_id_to_fresh_id| must be unique
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Inserts a copy of the propagated instruction into each |block_id|'s
+  // predecessor. Replaces the original instruction with an OpPhi referring
+  // inserted copies.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if this transformation can be applied to the block with id
+  // |block_id|. Concretely, returns true iff:
+  // - |block_id| is a valid id of some block in the module
+  // - |block_id| has predecessors
+  // - |block_id| contains an instruction that can be propagated
+  // - it is possible to insert the propagated instruction into every
+  //   |block_id|'s predecessor
+  static bool IsApplicableToBlock(opt::IRContext* ir_context,
+                                  uint32_t block_id);
+
+ private:
+  // Returns the instruction that will be propagated into the predecessors of
+  // the |block_id|. Returns nullptr if no such an instruction exists.
+  static opt::Instruction* GetInstructionToPropagate(opt::IRContext* ir_context,
+                                                     uint32_t block_id);
+
+  // Returns true if |opcode| is supported by this transformation.
+  static bool IsOpcodeSupported(SpvOp opcode);
+
+  protobufs::TransformationPropagateInstructionUp message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_UP_H_
diff --git a/source/fuzz/transformation_push_id_through_variable.cpp b/source/fuzz/transformation_push_id_through_variable.cpp
index 647bfa0..cdc40aa 100644
--- a/source/fuzz/transformation_push_id_through_variable.cpp
+++ b/source/fuzz/transformation_push_id_through_variable.cpp
@@ -38,8 +38,7 @@
 }
 
 bool TransformationPushIdThroughVariable::IsApplicable(
-    opt::IRContext* ir_context,
-    const TransformationContext& transformation_context) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // |message_.value_synonym_id| and |message_.variable_id| must be fresh.
   if (!fuzzerutil::IsFreshId(ir_context, message_.value_synonym_id()) ||
       !fuzzerutil::IsFreshId(ir_context, message_.variable_id())) {
@@ -74,14 +73,6 @@
     return false;
   }
 
-  // We should be able to create a synonym of |value_id| if it's not irrelevant.
-  if (!transformation_context.GetFactManager()->IdIsIrrelevant(
-          message_.value_id()) &&
-      !fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
-                                    value_instruction)) {
-    return false;
-  }
-
   // A pointer type instruction pointing to the value type must be defined.
   auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
       ir_context, value_instruction->type_id(),
@@ -153,13 +144,16 @@
 
   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 
-  if (!transformation_context->GetFactManager()->IdIsIrrelevant(
-          message_.value_id())) {
+  // 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) &&
+      !transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.value_synonym_id())) {
     // Adds the fact that |message_.value_synonym_id|
     // and |message_.value_id| are synonymous.
     transformation_context->GetFactManager()->AddFactDataSynonym(
         MakeDataDescriptor(message_.value_synonym_id(), {}),
-        MakeDataDescriptor(message_.value_id(), {}), ir_context);
+        MakeDataDescriptor(message_.value_id(), {}));
   }
 }
 
@@ -170,5 +164,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationPushIdThroughVariable::GetFreshIds()
+    const {
+  return {message_.value_synonym_id(), message_.variable_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_push_id_through_variable.h b/source/fuzz/transformation_push_id_through_variable.h
index f49db31..d055825 100644
--- a/source/fuzz/transformation_push_id_through_variable.h
+++ b/source/fuzz/transformation_push_id_through_variable.h
@@ -52,10 +52,12 @@
 
   // Stores |value_id| to |variable_id|, loads |variable_id| to
   // |value_synonym_id|. Adds the fact that |value_synonym_id| and |value_id|
-  // are synonymous if |value_id| is not irrelevant.
+  // are synonymous if |value_id| and |value_synonym_id| are not irrelevant.
   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:
diff --git a/source/fuzz/transformation_record_synonymous_constants.cpp b/source/fuzz/transformation_record_synonymous_constants.cpp
index a93e1d4..30ea94b 100644
--- a/source/fuzz/transformation_record_synonymous_constants.cpp
+++ b/source/fuzz/transformation_record_synonymous_constants.cpp
@@ -52,12 +52,12 @@
 }
 
 void TransformationRecordSynonymousConstants::Apply(
-    opt::IRContext* ir_context,
+    opt::IRContext* /*unused*/,
     TransformationContext* transformation_context) const {
   // Add the fact to the fact manager
   transformation_context->GetFactManager()->AddFactDataSynonym(
       MakeDataDescriptor(message_.constant1_id(), {}),
-      MakeDataDescriptor(message_.constant2_id(), {}), ir_context);
+      MakeDataDescriptor(message_.constant2_id(), {}));
 }
 
 protobufs::Transformation TransformationRecordSynonymousConstants::ToMessage()
@@ -81,7 +81,10 @@
   auto constant1 = ir_context->get_constant_mgr()->GetConstantFromInst(def_1);
   auto constant2 = ir_context->get_constant_mgr()->GetConstantFromInst(def_2);
 
-  assert(constant1 && constant2 && "The ids must refer to constants.");
+  // The ids must refer to constants.
+  if (!constant1 || !constant2) {
+    return false;
+  }
 
   // The types must be compatible.
   if (!fuzzerutil::TypesAreEqualUpToSign(ir_context, def_1->type_id(),
@@ -100,11 +103,14 @@
 
   // If the constants are scalar, they are equal iff their words are the same
   if (auto scalar1 = constant1->AsScalarConstant()) {
+    // Either both or neither constant is scalar since we've already checked
+    // that their types are compatible.
+    assert(constant2->AsScalarConstant() && "Both constants must be scalar");
     return scalar1->words() == constant2->AsScalarConstant()->words();
   }
 
   // The only remaining possibility is that the constants are composite
-  assert(constant1->AsCompositeConstant() &&
+  assert(constant1->AsCompositeConstant() && constant2->AsCompositeConstant() &&
          "Equivalence of constants can only be checked with scalar, composite "
          "or null constants.");
 
@@ -122,5 +128,10 @@
   return true;
 }
 
+std::unordered_set<uint32_t>
+TransformationRecordSynonymousConstants::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_record_synonymous_constants.h b/source/fuzz/transformation_record_synonymous_constants.h
index 8cff0cd..4376c87 100644
--- a/source/fuzz/transformation_record_synonymous_constants.h
+++ b/source/fuzz/transformation_record_synonymous_constants.h
@@ -52,6 +52,8 @@
   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:
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
new file mode 100644
index 0000000..a257515
--- /dev/null
+++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp
@@ -0,0 +1,237 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+const uint32_t kOpCompositeExtractIndexLowOrderBits = 0;
+const uint32_t kArithmeticInstructionIndexLeftInOperand = 0;
+const uint32_t kArithmeticInstructionIndexRightInOperand = 1;
+}  // namespace
+
+TransformationReplaceAddSubMulWithCarryingExtended::
+    TransformationReplaceAddSubMulWithCarryingExtended(
+        const spvtools::fuzz::protobufs::
+            TransformationReplaceAddSubMulWithCarryingExtended& message)
+    : message_(message) {}
+
+TransformationReplaceAddSubMulWithCarryingExtended::
+    TransformationReplaceAddSubMulWithCarryingExtended(uint32_t struct_fresh_id,
+                                                       uint32_t result_id) {
+  message_.set_struct_fresh_id(struct_fresh_id);
+  message_.set_result_id(result_id);
+}
+
+bool TransformationReplaceAddSubMulWithCarryingExtended::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // |message_.struct_fresh_id| must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id())) {
+    return false;
+  }
+
+  // |message_.result_id| must refer to a suitable OpIAdd, OpISub or OpIMul
+  // instruction. The instruction must be defined.
+  auto instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id());
+  if (instruction == nullptr) {
+    return false;
+  }
+  if (!TransformationReplaceAddSubMulWithCarryingExtended::
+          IsInstructionSuitable(ir_context, *instruction)) {
+    return false;
+  }
+
+  // The struct type for holding the intermediate result must exist in the
+  // module. The struct type is based on the operand type.
+  uint32_t operand_type_id = ir_context->get_def_use_mgr()
+                                 ->GetDef(instruction->GetSingleWordInOperand(
+                                     kArithmeticInstructionIndexLeftInOperand))
+                                 ->type_id();
+
+  uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
+      ir_context, {operand_type_id, operand_type_id});
+  if (struct_type_id == 0) {
+    return false;
+  }
+  return true;
+}
+
+void TransformationReplaceAddSubMulWithCarryingExtended::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // |message_.struct_fresh_id| must be fresh.
+  assert(fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id()) &&
+         "|message_.struct_fresh_id| must be fresh");
+
+  // Get the signedness of an operand if it is an int or the signedness of a
+  // component if it is a vector.
+  auto type_id =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id())->type_id();
+  auto type = ir_context->get_type_mgr()->GetType(type_id);
+  bool operand_is_signed;
+  if (type->kind() == opt::analysis::Type::kVector) {
+    auto operand_type = type->AsVector()->element_type();
+    operand_is_signed = operand_type->AsInteger()->IsSigned();
+  } else {
+    operand_is_signed = type->AsInteger()->IsSigned();
+  }
+
+  auto original_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id());
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.struct_fresh_id());
+
+  // Determine the opcode of the new instruction that computes the result into a
+  // struct.
+  SpvOp new_instruction_opcode;
+
+  switch (original_instruction->opcode()) {
+    case SpvOpIAdd:
+      new_instruction_opcode = SpvOpIAddCarry;
+      break;
+    case SpvOpISub:
+      new_instruction_opcode = SpvOpISubBorrow;
+      break;
+    case SpvOpIMul:
+      if (!operand_is_signed) {
+        new_instruction_opcode = SpvOpUMulExtended;
+      } else {
+        new_instruction_opcode = SpvOpSMulExtended;
+      }
+      break;
+    default:
+      assert(false && "The instruction has an unsupported opcode.");
+      return;
+  }
+  // Get the type of struct type id holding the intermediate result based on the
+  // operand type.
+  uint32_t operand_type_id =
+      ir_context->get_def_use_mgr()
+          ->GetDef(original_instruction->GetSingleWordInOperand(
+              kArithmeticInstructionIndexLeftInOperand))
+          ->type_id();
+
+  uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
+      ir_context, {operand_type_id, operand_type_id});
+  // Avoid unused variables in release mode.
+  (void)struct_type_id;
+  assert(struct_type_id && "The struct type must exist in the module.");
+
+  // Insert the new instruction that computes the result into a struct before
+  // the |original_instruction|.
+  original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, new_instruction_opcode, struct_type_id,
+      message_.struct_fresh_id(),
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID,
+            {original_instruction->GetSingleWordInOperand(
+                kArithmeticInstructionIndexLeftInOperand)}},
+           {SPV_OPERAND_TYPE_ID,
+            {original_instruction->GetSingleWordInOperand(
+                kArithmeticInstructionIndexRightInOperand)}}})));
+
+  // Insert the OpCompositeExtract after the added instruction. This instruction
+  // takes the first component of the struct which represents low-order bits of
+  // the operation. This is the original result.
+  original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpCompositeExtract, original_instruction->type_id(),
+      message_.result_id(),
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID, {message_.struct_fresh_id()}},
+           {SPV_OPERAND_TYPE_LITERAL_INTEGER,
+            {kOpCompositeExtractIndexLowOrderBits}}})));
+
+  // Remove the original instruction.
+  ir_context->KillInst(original_instruction);
+
+  // We have modified the module so most analyzes are now invalid.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+bool TransformationReplaceAddSubMulWithCarryingExtended::IsInstructionSuitable(
+    opt::IRContext* ir_context, const opt::Instruction& instruction) {
+  auto instruction_opcode = instruction.opcode();
+
+  // Only instructions OpIAdd, OpISub, OpIMul are supported.
+  switch (instruction_opcode) {
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+      break;
+    default:
+      return false;
+  }
+  uint32_t operand_1_type_id =
+      ir_context->get_def_use_mgr()
+          ->GetDef(instruction.GetSingleWordInOperand(
+              kArithmeticInstructionIndexLeftInOperand))
+          ->type_id();
+
+  uint32_t operand_2_type_id =
+      ir_context->get_def_use_mgr()
+          ->GetDef(instruction.GetSingleWordInOperand(
+              kArithmeticInstructionIndexRightInOperand))
+          ->type_id();
+
+  uint32_t result_type_id = instruction.type_id();
+
+  // Both type ids of the operands and the result type ids must be equal.
+  if (operand_1_type_id != operand_2_type_id) {
+    return false;
+  }
+  if (operand_2_type_id != result_type_id) {
+    return false;
+  }
+
+  // In case of OpIAdd and OpISub, the type must be unsigned.
+  auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
+
+  switch (instruction_opcode) {
+    case SpvOpIAdd:
+    case SpvOpISub: {
+      // In case of OpIAdd and OpISub if the operand is a vector, the component
+      // type must be unsigned. Otherwise (if the operand is an int), the
+      // operand must be unsigned.
+      bool operand_is_signed =
+          type->AsVector()
+              ? type->AsVector()->element_type()->AsInteger()->IsSigned()
+              : type->AsInteger()->IsSigned();
+      if (operand_is_signed) {
+        return false;
+      }
+    } break;
+    default:
+      break;
+  }
+  return true;
+}
+
+protobufs::Transformation
+TransformationReplaceAddSubMulWithCarryingExtended::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_add_sub_mul_with_carrying_extended() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t>
+TransformationReplaceAddSubMulWithCarryingExtended::GetFreshIds() const {
+  return {message_.struct_fresh_id()};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
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
new file mode 100644
index 0000000..243542c
--- /dev/null
+++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_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 TransformationReplaceAddSubMulWithCarryingExtended
+    : public Transformation {
+ public:
+  explicit TransformationReplaceAddSubMulWithCarryingExtended(
+      const protobufs::TransformationReplaceAddSubMulWithCarryingExtended&
+          message);
+
+  explicit TransformationReplaceAddSubMulWithCarryingExtended(
+      uint32_t struct_fresh_id, uint32_t result_id);
+
+  // - |message_.struct_fresh_id| must be fresh.
+  // - |message_.result_id| must refer to an OpIAdd or OpISub or OpIMul
+  //   instruction. In this instruction the result type id and the type ids of
+  //   the operands must be the same.
+  // - The type of struct holding the intermediate result must exists in the
+  //   module.
+  // - For OpIAdd, OpISub both operands must be unsigned.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // A transformation that replaces instructions OpIAdd, OpISub, OpIMul with
+  // pairs of instructions. The first one (OpIAddCarry, OpISubBorrow,
+  // OpUMulExtended, OpSMulExtended) computes the result into a struct. The
+  // second one extracts the appropriate component from the struct to yield the
+  // original result.
+  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 if an OpIAdd, OpISub or OpIMul instruction can be used by the
+  // transformation.
+  bool static IsInstructionSuitable(opt::IRContext* ir_context,
+                                    const opt::Instruction& instruction);
+
+ private:
+  protobufs::TransformationReplaceAddSubMulWithCarryingExtended message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H_
diff --git a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
index 6e22e7c..b458b56 100644
--- a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.cpp
@@ -318,5 +318,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationReplaceBooleanConstantWithConstantBinary::GetFreshIds() const {
+  return {message_.fresh_id_for_binary_operation()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
index 3abb485..a0ece7f 100644
--- a/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
+++ b/source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h
@@ -67,6 +67,8 @@
       opt::IRContext* ir_context,
       TransformationContext* transformation_context) const;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
  private:
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
new file mode 100644
index 0000000..e809012
--- /dev/null
+++ b/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.cpp
@@ -0,0 +1,169 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationReplaceBranchFromDeadBlockWithExit::
+    TransformationReplaceBranchFromDeadBlockWithExit(
+        const spvtools::fuzz::protobufs::
+            TransformationReplaceBranchFromDeadBlockWithExit& message)
+    : message_(message) {}
+
+TransformationReplaceBranchFromDeadBlockWithExit::
+    TransformationReplaceBranchFromDeadBlockWithExit(uint32_t block_id,
+                                                     SpvOp opcode,
+                                                     uint32_t return_value_id) {
+  message_.set_block_id(block_id);
+  message_.set_opcode(opcode);
+  message_.set_return_value_id(return_value_id);
+}
+
+bool TransformationReplaceBranchFromDeadBlockWithExit::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // The block whose terminator is to be changed must exist.
+  auto block = ir_context->get_instr_block(message_.block_id());
+  if (!block) {
+    return false;
+  }
+  if (!BlockIsSuitable(ir_context, transformation_context, *block)) {
+    return false;
+  }
+  auto function_return_type_id = block->GetParent()->type_id();
+  switch (message_.opcode()) {
+    case SpvOpKill:
+      for (auto& entry_point : ir_context->module()->entry_points()) {
+        if (entry_point.GetSingleWordInOperand(0) !=
+            SpvExecutionModelFragment) {
+          // OpKill is only allowed in a fragment shader.  This is a
+          // conservative check: if the module contains a non-fragment entry
+          // point then adding an OpKill might lead to OpKill being used in a
+          // non-fragment shader.
+          return false;
+        }
+      }
+      break;
+    case SpvOpReturn:
+      if (ir_context->get_def_use_mgr()
+              ->GetDef(function_return_type_id)
+              ->opcode() != SpvOpTypeVoid) {
+        // OpReturn is only allowed in a function with void return type.
+        return false;
+      }
+      break;
+    case SpvOpReturnValue: {
+      // If the terminator is to be changed to OpReturnValue, with
+      // |message_.return_value_id| being the value that will be returned, then
+      // |message_.return_value_id| must have a compatible type and be available
+      // at the block terminator.
+      auto return_value =
+          ir_context->get_def_use_mgr()->GetDef(message_.return_value_id());
+      if (!return_value || return_value->type_id() != function_return_type_id) {
+        return false;
+      }
+      if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+              ir_context, block->terminator(), message_.return_value_id())) {
+        return false;
+      }
+      break;
+    }
+    default:
+      assert(message_.opcode() == SpvOpUnreachable &&
+             "Invalid early exit opcode.");
+      break;
+  }
+  return true;
+}
+
+void TransformationReplaceBranchFromDeadBlockWithExit::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // If the successor block has OpPhi instructions then arguments related to
+  // |message_.block_id| need to be removed from these instruction.
+  auto block = ir_context->get_instr_block(message_.block_id());
+  assert(block->terminator()->opcode() == SpvOpBranch &&
+         "Precondition: the block must end with OpBranch.");
+  auto successor = ir_context->get_instr_block(
+      block->terminator()->GetSingleWordInOperand(0));
+  successor->ForEachPhiInst([block](opt::Instruction* phi_inst) {
+    opt::Instruction::OperandList new_phi_in_operands;
+    for (uint32_t i = 0; i < phi_inst->NumInOperands(); i += 2) {
+      if (phi_inst->GetSingleWordInOperand(i + 1) == block->id()) {
+        continue;
+      }
+      new_phi_in_operands.emplace_back(phi_inst->GetInOperand(i));
+      new_phi_in_operands.emplace_back(phi_inst->GetInOperand(i + 1));
+    }
+    assert(new_phi_in_operands.size() == phi_inst->NumInOperands() - 2);
+    phi_inst->SetInOperands(std::move(new_phi_in_operands));
+  });
+
+  // Rewrite the terminator of |message_.block_id|.
+  opt::Instruction::OperandList new_terminator_in_operands;
+  if (message_.opcode() == SpvOpReturnValue) {
+    new_terminator_in_operands.push_back(
+        {SPV_OPERAND_TYPE_ID, {message_.return_value_id()}});
+  }
+  auto terminator = block->terminator();
+  terminator->SetOpcode(static_cast<SpvOp>(message_.opcode()));
+  terminator->SetInOperands(std::move(new_terminator_in_operands));
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+std::unordered_set<uint32_t>
+TransformationReplaceBranchFromDeadBlockWithExit::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
+protobufs::Transformation
+TransformationReplaceBranchFromDeadBlockWithExit::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_branch_from_dead_block_with_exit() = message_;
+  return result;
+}
+
+bool TransformationReplaceBranchFromDeadBlockWithExit::BlockIsSuitable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context,
+    const opt::BasicBlock& block) {
+  // The block must be dead.
+  if (!transformation_context.GetFactManager()->BlockIsDead(block.id())) {
+    return false;
+  }
+  // The block's terminator must be OpBranch.
+  if (block.terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+  if (ir_context->GetStructuredCFGAnalysis()->IsInContinueConstruct(
+          block.id())) {
+    // Early exits from continue constructs are not allowed as they would break
+    // the SPIR-V structured control flow rules.
+    return false;
+  }
+  // We only allow changing OpBranch to an early terminator if the target of the
+  // OpBranch has at least one other predecessor.
+  auto successor = ir_context->get_instr_block(
+      block.terminator()->GetSingleWordInOperand(0));
+  if (ir_context->cfg()->preds(successor->id()).size() < 2) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
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
new file mode 100644
index 0000000..e1418c9
--- /dev/null
+++ b/source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_BRANCH_FROM_DEAD_BLOCK_WITH_EXIT_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_BRANCH_FROM_DEAD_BLOCK_WITH_EXIT_H_
+
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/basic_block.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceBranchFromDeadBlockWithExit : public Transformation {
+ public:
+  explicit TransformationReplaceBranchFromDeadBlockWithExit(
+      const protobufs::TransformationReplaceBranchFromDeadBlockWithExit&
+          message);
+
+  TransformationReplaceBranchFromDeadBlockWithExit(uint32_t block_id,
+                                                   SpvOp opcode,
+                                                   uint32_t return_value_id);
+
+  // - |message_.block_id| must be the id of a dead block that is not part of
+  //   a continue construct
+  // - |message_.block_id| must end with OpBranch
+  // - The successor of |message_.block_id| must have at least one other
+  //   predecessor
+  // - |message_.opcode()| must be one of OpKill, OpReturn, OpReturnValue and
+  //   OpUnreachable
+  // - |message_.opcode()| can only be OpKill 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
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Changes the terminator of |message_.block_id| to have opcode
+  // |message_.opcode|, additionally with input operand
+  // |message_.return_value_id| in the case that |message_.opcode| is
+  // OpReturnValue.
+  //
+  // If |message_.block_id|'s successor starts with OpPhi instructions these are
+  // updated so that they no longer refer to |message_.block_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;
+
+  // Returns true if and only if |block| meets the criteria for having its
+  // terminator replaced with an early exit (see IsApplicable for details of the
+  // criteria.)
+  static bool BlockIsSuitable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context,
+      const opt::BasicBlock& block);
+
+ private:
+  protobufs::TransformationReplaceBranchFromDeadBlockWithExit message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_BRANCH_FROM_DEAD_BLOCK_WITH_EXIT_H_
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.cpp b/source/fuzz/transformation_replace_constant_with_uniform.cpp
index a8f9495..95932bf 100644
--- a/source/fuzz/transformation_replace_constant_with_uniform.cpp
+++ b/source/fuzz/transformation_replace_constant_with_uniform.cpp
@@ -90,6 +90,40 @@
                                       operands_for_load);
 }
 
+opt::Instruction*
+TransformationReplaceConstantWithUniform::GetInsertBeforeInstruction(
+    opt::IRContext* ir_context) const {
+  auto* result =
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
+  if (!result) {
+    return nullptr;
+  }
+
+  // The use might be in an OpPhi instruction.
+  if (result->opcode() == SpvOpPhi) {
+    // OpPhi instructions must be the first instructions in a block. Thus, we
+    // can't insert above the OpPhi instruction. Given the predecessor block
+    // that corresponds to the id use, get the last instruction in that block
+    // above which we can insert OpAccessChain and OpLoad.
+    return fuzzerutil::GetLastInsertBeforeInstruction(
+        ir_context,
+        result->GetSingleWordInOperand(
+            message_.id_use_descriptor().in_operand_index() + 1),
+        SpvOpLoad);
+  }
+
+  // The only operand that we could've replaced in the OpBranchConditional is
+  // the condition id. But that operand has a boolean type and uniform variables
+  // can't store booleans (see the spec on OpTypeBool). Thus, |result| can't be
+  // an OpBranchConditional.
+  assert(result->opcode() != SpvOpBranchConditional &&
+         "OpBranchConditional has no operands to replace");
+
+  assert(fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, result) &&
+         "We should be able to insert OpLoad and OpAccessChain at this point");
+  return result;
+}
+
 bool TransformationReplaceConstantWithUniform::IsApplicable(
     opt::IRContext* ir_context,
     const TransformationContext& transformation_context) const {
@@ -123,7 +157,7 @@
   // by the uniform buffer element descriptor will hold a scalar value.
   auto constant_id_associated_with_uniform =
       transformation_context.GetFactManager()->GetConstantFromUniformDescriptor(
-          ir_context, message_.uniform_descriptor());
+          message_.uniform_descriptor());
   if (!constant_id_associated_with_uniform) {
     return false;
   }
@@ -188,6 +222,12 @@
     }
   }
 
+  // Once all checks are completed, we should be able to safely insert
+  // OpAccessChain and OpLoad into the module.
+  assert(GetInsertBeforeInstruction(ir_context) &&
+         "There must exist an instruction that we can use to insert "
+         "OpAccessChain and OpLoad above");
+
   return true;
 }
 
@@ -195,7 +235,7 @@
     spvtools::opt::IRContext* ir_context,
     TransformationContext* /*unused*/) const {
   // Get the instruction that contains the id use we wish to replace.
-  auto instruction_containing_constant_use =
+  auto* instruction_containing_constant_use =
       FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
   assert(instruction_containing_constant_use &&
          "Precondition requires that the id use can be found.");
@@ -210,12 +250,17 @@
           ->GetDef(message_.id_use_descriptor().id_of_interest())
           ->type_id();
 
+  // Get an instruction that will be used to insert OpAccessChain and OpLoad.
+  auto* insert_before_inst = GetInsertBeforeInstruction(ir_context);
+  assert(insert_before_inst &&
+         "There must exist an insertion point for OpAccessChain and OpLoad");
+
   // Add an access chain instruction to target the uniform element.
-  instruction_containing_constant_use->InsertBefore(
+  insert_before_inst->InsertBefore(
       MakeAccessChainInstruction(ir_context, constant_type_id));
 
   // Add a load from this access chain.
-  instruction_containing_constant_use->InsertBefore(
+  insert_before_inst->InsertBefore(
       MakeLoadInstruction(ir_context, constant_type_id));
 
   // Adjust the instruction containing the usage of the constant so that this
@@ -240,5 +285,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationReplaceConstantWithUniform::GetFreshIds() const {
+  return {message_.fresh_id_for_access_chain(), message_.fresh_id_for_load()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_constant_with_uniform.h b/source/fuzz/transformation_replace_constant_with_uniform.h
index b72407c..9e09748 100644
--- a/source/fuzz/transformation_replace_constant_with_uniform.h
+++ b/source/fuzz/transformation_replace_constant_with_uniform.h
@@ -17,7 +17,6 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
 #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_CONSTANT_WITH_UNIFORM_H_
 
-#include "source/fuzz/fact_manager.h"
 #include "source/fuzz/id_use_descriptor.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
@@ -72,6 +71,8 @@
   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:
@@ -84,6 +85,11 @@
   std::unique_ptr<opt::Instruction> MakeLoadInstruction(
       spvtools::opt::IRContext* ir_context, uint32_t constant_type_id) const;
 
+  // OpAccessChain and OpLoad will be inserted above the instruction returned
+  // by this function. Returns nullptr if no such instruction is present.
+  opt::Instruction* GetInsertBeforeInstruction(
+      opt::IRContext* ir_context) const;
+
   protobufs::TransformationReplaceConstantWithUniform message_;
 };
 
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 bf6996a..936b054 100644
--- a/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp
+++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.cpp
@@ -61,7 +61,7 @@
          copy_memory_instruction->opcode() == SpvOpCopyMemory &&
          "The required OpCopyMemory instruction must be defined.");
 
-  // Coherence check: Both operands must be pointers.
+  // Integrity check: Both operands must be pointers.
 
   // Get types of ids used as a source and target of |copy_memory_instruction|.
   auto target = ir_context->get_def_use_mgr()->GetDef(
@@ -83,7 +83,7 @@
          source_type_opcode == SpvOpTypePointer &&
          "Operands must be of type OpTypePointer");
 
-  // Coherence check: |source| and |target| must point to the same type.
+  // Integrity check: |source| and |target| must point to the same type.
   uint32_t target_pointee_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
       ir_context, target->type_id());
   uint32_t source_pointee_type = fuzzerutil::GetPointeeTypeIdFromPointerType(
@@ -123,5 +123,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationReplaceCopyMemoryWithLoadStore::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
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 70120f8..67d349f 100644
--- a/source/fuzz/transformation_replace_copy_memory_with_load_store.h
+++ b/source/fuzz/transformation_replace_copy_memory_with_load_store.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H
-#define SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H_
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
@@ -45,6 +45,8 @@
   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:
@@ -54,4 +56,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_MEMORY_WITH_LOAD_STORE_H_
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 05e8cda..54c99d5 100644
--- a/source/fuzz/transformation_replace_copy_object_with_store_load.cpp
+++ b/source/fuzz/transformation_replace_copy_object_with_store_load.cpp
@@ -59,14 +59,6 @@
     return false;
   }
 
-  // It must be valid to insert the OpStore and OpLoad instruction before it.
-  if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpStore,
-                                                    copy_object_instruction) ||
-      !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad,
-                                                    copy_object_instruction)) {
-    return false;
-  }
-
   // A pointer type instruction pointing to the value type must be defined.
   auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
       ir_context, copy_object_instruction->type_id(),
@@ -97,7 +89,7 @@
          copy_object_instruction->opcode() == SpvOpCopyObject &&
          "The required OpCopyObject instruction must be defined.");
   // Get id used as a source by the OpCopyObject instruction.
-  uint32_t src_operand = copy_object_instruction->GetSingleWordOperand(2);
+  uint32_t src_operand = copy_object_instruction->GetSingleWordInOperand(0);
   // A pointer type instruction pointing to the value type must be defined.
   auto pointer_type_id = fuzzerutil::MaybeGetPointerType(
       ir_context, copy_object_instruction->type_id(),
@@ -137,11 +129,15 @@
 
   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
 
-  // Adds the fact that |message_.copy_object_result_id|
-  // and src_operand (id used by OpCopyObject) are synonymous.
-  transformation_context->GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(message_.copy_object_result_id(), {}),
-      MakeDataDescriptor(src_operand, {}), ir_context);
+  if (!transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.copy_object_result_id()) &&
+      !transformation_context->GetFactManager()->IdIsIrrelevant(src_operand)) {
+    // Adds the fact that |message_.copy_object_result_id|
+    // and src_operand (id used by OpCopyObject) are synonymous.
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(message_.copy_object_result_id(), {}),
+        MakeDataDescriptor(src_operand, {}));
+  }
 }
 
 protobufs::Transformation
@@ -151,5 +147,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationReplaceCopyObjectWithStoreLoad::GetFreshIds() const {
+  return {message_.fresh_variable_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
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 db9c74e..a90905c 100644
--- a/source/fuzz/transformation_replace_copy_object_with_store_load.h
+++ b/source/fuzz/transformation_replace_copy_object_with_store_load.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H
-#define SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H_
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
@@ -51,6 +51,8 @@
   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:
@@ -60,4 +62,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_COPY_OBJECT_WITH_STORE_LOAD_H_
diff --git a/source/fuzz/transformation_replace_id_with_synonym.cpp b/source/fuzz/transformation_replace_id_with_synonym.cpp
index 55607e1..24e079f 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.cpp
+++ b/source/fuzz/transformation_replace_id_with_synonym.cpp
@@ -66,18 +66,15 @@
   // 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 (type_id_of_interest != type_id_synonym &&
-      fuzzerutil::TypesAreEqualUpToSign(ir_context, type_id_of_interest,
-                                        type_id_synonym) &&
-      !IsAgnosticToSignednessOfOperand(
-          use_instruction->opcode(),
-          message_.id_use_descriptor().in_operand_index())) {
+  if (!TypesAreCompatible(ir_context, use_instruction->opcode(),
+                          message_.id_use_descriptor().in_operand_index(),
+                          type_id_of_interest, type_id_synonym)) {
     return false;
   }
 
   // Is the use suitable for being replaced in principle?
-  if (!UseCanBeReplacedWithSynonym(
-          ir_context, use_instruction,
+  if (!fuzzerutil::IdUseCanBeReplaced(
+          ir_context, transformation_context, use_instruction,
           message_.id_use_descriptor().in_operand_index())) {
     return false;
   }
@@ -109,97 +106,6 @@
   return result;
 }
 
-bool TransformationReplaceIdWithSynonym::UseCanBeReplacedWithSynonym(
-    opt::IRContext* ir_context, opt::Instruction* use_instruction,
-    uint32_t use_in_operand_index) {
-  if (use_instruction->opcode() == SpvOpAccessChain &&
-      use_in_operand_index > 0) {
-    // This is an access chain index.  If the (sub-)object being accessed by the
-    // given index has struct type then we cannot replace the use with a
-    // synonym, as the use needs to be an OpConstant.
-
-    // Get the top-level composite type that is being accessed.
-    auto object_being_accessed = ir_context->get_def_use_mgr()->GetDef(
-        use_instruction->GetSingleWordInOperand(0));
-    auto pointer_type =
-        ir_context->get_type_mgr()->GetType(object_being_accessed->type_id());
-    assert(pointer_type->AsPointer());
-    auto composite_type_being_accessed =
-        pointer_type->AsPointer()->pointee_type();
-
-    // Now walk the access chain, tracking the type of each sub-object of the
-    // composite that is traversed, until the index of interest is reached.
-    for (uint32_t index_in_operand = 1; index_in_operand < use_in_operand_index;
-         index_in_operand++) {
-      // For vectors, matrices and arrays, getting the type of the sub-object is
-      // trivial. For the struct case, the sub-object type is field-sensitive,
-      // and depends on the constant index that is used.
-      if (composite_type_being_accessed->AsVector()) {
-        composite_type_being_accessed =
-            composite_type_being_accessed->AsVector()->element_type();
-      } else if (composite_type_being_accessed->AsMatrix()) {
-        composite_type_being_accessed =
-            composite_type_being_accessed->AsMatrix()->element_type();
-      } else if (composite_type_being_accessed->AsArray()) {
-        composite_type_being_accessed =
-            composite_type_being_accessed->AsArray()->element_type();
-      } else if (composite_type_being_accessed->AsRuntimeArray()) {
-        composite_type_being_accessed =
-            composite_type_being_accessed->AsRuntimeArray()->element_type();
-      } else {
-        assert(composite_type_being_accessed->AsStruct());
-        auto constant_index_instruction = ir_context->get_def_use_mgr()->GetDef(
-            use_instruction->GetSingleWordInOperand(index_in_operand));
-        assert(constant_index_instruction->opcode() == SpvOpConstant);
-        uint32_t member_index =
-            constant_index_instruction->GetSingleWordInOperand(0);
-        composite_type_being_accessed =
-            composite_type_being_accessed->AsStruct()
-                ->element_types()[member_index];
-      }
-    }
-
-    // We have found the composite type being accessed by the index we are
-    // considering replacing. If it is a struct, then we cannot do the
-    // replacement as struct indices must be constants.
-    if (composite_type_being_accessed->AsStruct()) {
-      return false;
-    }
-  }
-
-  if (use_instruction->opcode() == SpvOpFunctionCall &&
-      use_in_operand_index > 0) {
-    // This is a function call argument.  It is not allowed to have pointer
-    // type.
-
-    // Get the definition of the function being called.
-    auto function = ir_context->get_def_use_mgr()->GetDef(
-        use_instruction->GetSingleWordInOperand(0));
-    // From the function definition, get the function type.
-    auto function_type = ir_context->get_def_use_mgr()->GetDef(
-        function->GetSingleWordInOperand(1));
-    // OpTypeFunction's 0-th input operand is the function return type, and the
-    // function argument types follow. Because the arguments to OpFunctionCall
-    // start from input operand 1, we can use |use_in_operand_index| to get the
-    // type associated with this function argument.
-    auto parameter_type = ir_context->get_type_mgr()->GetType(
-        function_type->GetSingleWordInOperand(use_in_operand_index));
-    if (parameter_type->AsPointer()) {
-      return false;
-    }
-  }
-
-  if (use_instruction->opcode() == SpvOpImageTexelPointer &&
-      use_in_operand_index == 2) {
-    // The OpImageTexelPointer instruction has a Sample parameter that in some
-    // situations must be an id for the value 0.  To guard against disrupting
-    // that requirement, we do not replace this argument to that instruction.
-    return false;
-  }
-
-  return true;
-}
-
 // 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.
@@ -241,5 +147,22 @@
   }
 }
 
+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>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_id_with_synonym.h b/source/fuzz/transformation_replace_id_with_synonym.h
index 78878b2..3101710 100644
--- a/source/fuzz/transformation_replace_id_with_synonym.h
+++ b/source/fuzz/transformation_replace_id_with_synonym.h
@@ -32,15 +32,12 @@
       protobufs::IdUseDescriptor id_use_descriptor, uint32_t synonymous_id);
 
   // - The fact manager must know that the id identified by
-  //   |message_.id_use_descriptor| is synonomous with
-  //   |message_.synonymous_id|.
+  //   |message_.id_use_descriptor| is synonomous with |message_.synonymous_id|.
   // - Replacing the id in |message_.id_use_descriptor| by
   //   |message_.synonymous_id| must respect SPIR-V's rules about uses being
   //   dominated by their definitions.
-  // - The id must not be an index into an access chain whose base object has
-  //   struct type, as such indices must be constants.
-  // - The id must not be a pointer argument to a function call (because the
-  //   synonym might not be a memory object declaration).
+  // - The id use must be replaceable in principle. See
+  //   fuzzerutil::IdUseCanBeReplaced for details.
   // - |fresh_id_for_temporary| must be 0.
   bool IsApplicable(
       opt::IRContext* ir_context,
@@ -51,19 +48,19 @@
   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 various conditions hold related to the acceptability of
-  // replacing the id use at |use_in_operand_index| of |use_instruction| with
-  // a synonym.  In particular, this checks that:
-  // - the id use is not an index into a struct field in an OpAccessChain - such
-  //   indices must be constants, so it is dangerous to replace them.
-  // - the id use is not a pointer function call argument, on which there are
-  //   restrictions that make replacement problematic.
-  static bool UseCanBeReplacedWithSynonym(opt::IRContext* ir_context,
-                                          opt::Instruction* use_instruction,
-                                          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).
+  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|.
@@ -71,7 +68,6 @@
   static bool IsAgnosticToSignednessOfOperand(SpvOp opcode,
                                               uint32_t use_in_operand_index);
 
- private:
   protobufs::TransformationReplaceIdWithSynonym message_;
 };
 
diff --git a/source/fuzz/transformation_replace_irrelevant_id.cpp b/source/fuzz/transformation_replace_irrelevant_id.cpp
new file mode 100644
index 0000000..27f56eb
--- /dev/null
+++ b/source/fuzz/transformation_replace_irrelevant_id.cpp
@@ -0,0 +1,135 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_irrelevant_id.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId(
+    const protobufs::TransformationReplaceIrrelevantId& message)
+    : message_(message) {}
+
+TransformationReplaceIrrelevantId::TransformationReplaceIrrelevantId(
+    const protobufs::IdUseDescriptor& id_use_descriptor,
+    uint32_t replacement_id) {
+  *message_.mutable_id_use_descriptor() = id_use_descriptor;
+  message_.set_replacement_id(replacement_id);
+}
+
+bool TransformationReplaceIrrelevantId::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  auto id_of_interest = message_.id_use_descriptor().id_of_interest();
+
+  // The id must be irrelevant.
+  if (!transformation_context.GetFactManager()->IdIsIrrelevant(
+          id_of_interest)) {
+    return false;
+  }
+
+  // Find the instruction containing the id use, which must exist.
+  auto use_instruction =
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
+  if (!use_instruction) {
+    return false;
+  }
+
+  // Check that the replacement id exists and retrieve its definition.
+  auto replacement_id_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.replacement_id());
+  if (!replacement_id_def) {
+    return false;
+  }
+
+  // The type of the id of interest and of the replacement id must be the same.
+  uint32_t type_id_of_interest =
+      ir_context->get_def_use_mgr()->GetDef(id_of_interest)->type_id();
+  uint32_t type_replacement_id = replacement_id_def->type_id();
+  if (type_id_of_interest != type_replacement_id) {
+    return false;
+  }
+
+  // The replacement id must not be the result of an OpFunction instruction.
+  if (replacement_id_def->opcode() == SpvOpFunction) {
+    return false;
+  }
+
+  // Consistency check: an irrelevant id cannot be a pointer.
+  assert(
+      !ir_context->get_type_mgr()->GetType(type_id_of_interest)->AsPointer() &&
+      "An irrelevant id cannot be a pointer");
+
+  uint32_t use_in_operand_index =
+      message_.id_use_descriptor().in_operand_index();
+
+  // The id use must be replaceable with any other id of the same type.
+  if (!fuzzerutil::IdUseCanBeReplaced(ir_context, transformation_context,
+                                      use_instruction, use_in_operand_index)) {
+    return false;
+  }
+
+  if (AttemptsToReplaceVariableInitializerWithNonConstant(
+          *use_instruction, *replacement_id_def)) {
+    return false;
+  }
+
+  // The id must be available to use at the use point.
+  return fuzzerutil::IdIsAvailableAtUse(
+      ir_context, use_instruction,
+      message_.id_use_descriptor().in_operand_index(),
+      message_.replacement_id());
+}
+
+void TransformationReplaceIrrelevantId::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* /* transformation_context */) const {
+  // Find the instruction.
+  auto instruction_to_change =
+      FindInstructionContainingUse(message_.id_use_descriptor(), ir_context);
+
+  // Replace the instruction.
+  instruction_to_change->SetInOperand(
+      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);
+}
+
+protobufs::Transformation TransformationReplaceIrrelevantId::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_irrelevant_id() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t> TransformationReplaceIrrelevantId::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
+bool TransformationReplaceIrrelevantId::
+    AttemptsToReplaceVariableInitializerWithNonConstant(
+        const opt::Instruction& use_instruction,
+        const opt::Instruction& replacement_for_use) {
+  return use_instruction.opcode() == SpvOpVariable &&
+         !spvOpcodeIsConstant(replacement_for_use.opcode());
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_irrelevant_id.h b/source/fuzz/transformation_replace_irrelevant_id.h
new file mode 100644
index 0000000..35b1987
--- /dev/null
+++ b/source/fuzz/transformation_replace_irrelevant_id.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceIrrelevantId : public Transformation {
+ public:
+  explicit TransformationReplaceIrrelevantId(
+      const protobufs::TransformationReplaceIrrelevantId& message);
+
+  TransformationReplaceIrrelevantId(
+      const protobufs::IdUseDescriptor& id_use_descriptor,
+      uint32_t replacement_id);
+
+  // - The id of interest in |message_.id_use_descriptor| is irrelevant
+  //   according to the fact manager.
+  // - The types of the original id and of the replacement ids are the same.
+  // - The replacement must not be the result id of an OpFunction instruction.
+  // - |message_.replacement_id| is available to use at the enclosing
+  //   instruction of |message_.id_use_descriptor|.
+  // - The original id is in principle replaceable with any other id of the same
+  //   type. See fuzzerutil::IdUseCanBeReplaced for details.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the use of an irrelevant id identified by
+  // |message_.id_use_descriptor| with the id |message_.replacement_id|, which
+  // has the same type as the id of interest.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if and only if |use_instruction| is OpVariable and
+  // |replacement_for_use| is not a constant instruction - i.e., if it would be
+  // illegal to replace the variable's initializer with the given instruction.
+  static bool AttemptsToReplaceVariableInitializerWithNonConstant(
+      const opt::Instruction& use_instruction,
+      const opt::Instruction& replacement_for_use);
+
+ private:
+  protobufs::TransformationReplaceIrrelevantId message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_IRRELEVANT_ID_H_
diff --git a/source/fuzz/transformation_replace_linear_algebra_instruction.cpp b/source/fuzz/transformation_replace_linear_algebra_instruction.cpp
index 76f083b..fc73a26 100644
--- a/source/fuzz/transformation_replace_linear_algebra_instruction.cpp
+++ b/source/fuzz/transformation_replace_linear_algebra_instruction.cpp
@@ -41,16 +41,8 @@
   auto instruction =
       FindInstruction(message_.instruction_descriptor(), ir_context);
 
-  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354):
-  // Right now we only support certain operations. When this issue is addressed
-  // the following conditional can use the function |spvOpcodeIsLinearAlgebra|.
-  // It must be a supported linear algebra instruction.
-  if (instruction->opcode() != SpvOpVectorTimesScalar &&
-      instruction->opcode() != SpvOpMatrixTimesScalar &&
-      instruction->opcode() != SpvOpVectorTimesMatrix &&
-      instruction->opcode() != SpvOpMatrixTimesVector &&
-      instruction->opcode() != SpvOpMatrixTimesMatrix &&
-      instruction->opcode() != SpvOpDot) {
+  // It must be a linear algebra instruction.
+  if (!spvOpcodeIsLinearAlgebra(instruction->opcode())) {
     return false;
   }
 
@@ -77,6 +69,9 @@
       FindInstruction(message_.instruction_descriptor(), ir_context);
 
   switch (linear_algebra_instruction->opcode()) {
+    case SpvOpTranspose:
+      ReplaceOpTranspose(ir_context, linear_algebra_instruction);
+      break;
     case SpvOpVectorTimesScalar:
       ReplaceOpVectorTimesScalar(ir_context, linear_algebra_instruction);
       break;
@@ -92,6 +87,9 @@
     case SpvOpMatrixTimesMatrix:
       ReplaceOpMatrixTimesMatrix(ir_context, linear_algebra_instruction);
       break;
+    case SpvOpOuterProduct:
+      ReplaceOpOuterProduct(ir_context, linear_algebra_instruction);
+      break;
     case SpvOpDot:
       ReplaceOpDot(ir_context, linear_algebra_instruction);
       break;
@@ -115,6 +113,24 @@
   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3354):
   // Right now we only support certain operations.
   switch (instruction->opcode()) {
+    case SpvOpTranspose: {
+      // For each matrix row, |2 * matrix_column_count| OpCompositeExtract and 1
+      // OpCompositeConstruct will be inserted.
+      auto matrix_instruction = ir_context->get_def_use_mgr()->GetDef(
+          instruction->GetSingleWordInOperand(0));
+      uint32_t matrix_column_count =
+          ir_context->get_type_mgr()
+              ->GetType(matrix_instruction->type_id())
+              ->AsMatrix()
+              ->element_count();
+      uint32_t matrix_row_count = ir_context->get_type_mgr()
+                                      ->GetType(matrix_instruction->type_id())
+                                      ->AsMatrix()
+                                      ->element_type()
+                                      ->AsVector()
+                                      ->element_count();
+      return matrix_row_count * (2 * matrix_column_count + 1);
+    }
     case SpvOpVectorTimesScalar:
       // For each vector component, 1 OpCompositeExtract and 1 OpFMul will be
       // inserted.
@@ -213,6 +229,26 @@
       return matrix_2_column_count *
              (2 + matrix_1_row_count * (5 * matrix_1_column_count - 1));
     }
+    case SpvOpOuterProduct: {
+      // For each |vector_2| component, |vector_1_component_count + 1|
+      // OpCompositeExtract, |vector_1_component_count| OpFMul and 1
+      // OpCompositeConstruct instructions will be inserted.
+      auto vector_1_instruction = ir_context->get_def_use_mgr()->GetDef(
+          instruction->GetSingleWordInOperand(0));
+      auto vector_2_instruction = ir_context->get_def_use_mgr()->GetDef(
+          instruction->GetSingleWordInOperand(1));
+      uint32_t vector_1_component_count =
+          ir_context->get_type_mgr()
+              ->GetType(vector_1_instruction->type_id())
+              ->AsVector()
+              ->element_count();
+      uint32_t vector_2_component_count =
+          ir_context->get_type_mgr()
+              ->GetType(vector_2_instruction->type_id())
+              ->AsVector()
+              ->element_count();
+      return 2 * vector_2_component_count * (vector_1_component_count + 1);
+    }
     case SpvOpDot:
       // For each pair of vector components, 2 OpCompositeExtract and 1 OpFMul
       // will be inserted. The first two OpFMul instructions will result the
@@ -233,6 +269,80 @@
   }
 }
 
+void TransformationReplaceLinearAlgebraInstruction::ReplaceOpTranspose(
+    opt::IRContext* ir_context,
+    opt::Instruction* linear_algebra_instruction) const {
+  // Gets OpTranspose instruction information.
+  auto matrix_instruction = ir_context->get_def_use_mgr()->GetDef(
+      linear_algebra_instruction->GetSingleWordInOperand(0));
+  uint32_t matrix_column_count = ir_context->get_type_mgr()
+                                     ->GetType(matrix_instruction->type_id())
+                                     ->AsMatrix()
+                                     ->element_count();
+  auto matrix_column_type = ir_context->get_type_mgr()
+                                ->GetType(matrix_instruction->type_id())
+                                ->AsMatrix()
+                                ->element_type();
+  auto matrix_column_component_type =
+      matrix_column_type->AsVector()->element_type();
+  uint32_t matrix_row_count = matrix_column_type->AsVector()->element_count();
+  auto resulting_matrix_column_type =
+      ir_context->get_type_mgr()
+          ->GetType(linear_algebra_instruction->type_id())
+          ->AsMatrix()
+          ->element_type();
+
+  uint32_t fresh_id_index = 0;
+  std::vector<uint32_t> result_column_ids(matrix_row_count);
+  for (uint32_t i = 0; i < matrix_row_count; i++) {
+    std::vector<uint32_t> column_component_ids(matrix_column_count);
+    for (uint32_t j = 0; j < matrix_column_count; j++) {
+      // Extracts the matrix column.
+      uint32_t matrix_column_id = message_.fresh_ids(fresh_id_index++);
+      linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpCompositeExtract,
+          ir_context->get_type_mgr()->GetId(matrix_column_type),
+          matrix_column_id,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {matrix_instruction->result_id()}},
+               {SPV_OPERAND_TYPE_LITERAL_INTEGER, {j}}})));
+
+      // Extracts the matrix column component.
+      column_component_ids[j] = message_.fresh_ids(fresh_id_index++);
+      linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpCompositeExtract,
+          ir_context->get_type_mgr()->GetId(matrix_column_component_type),
+          column_component_ids[j],
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {matrix_column_id}},
+               {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}})));
+    }
+
+    // Inserts the resulting matrix column.
+    opt::Instruction::OperandList in_operands;
+    for (auto& column_component_id : column_component_ids) {
+      in_operands.push_back({SPV_OPERAND_TYPE_ID, {column_component_id}});
+    }
+    result_column_ids[i] = message_.fresh_ids(fresh_id_index++);
+    linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpCompositeConstruct,
+        ir_context->get_type_mgr()->GetId(resulting_matrix_column_type),
+        result_column_ids[i], opt::Instruction::OperandList(in_operands)));
+  }
+
+  // The OpTranspose instruction is changed to an OpCompositeConstruct
+  // instruction.
+  linear_algebra_instruction->SetOpcode(SpvOpCompositeConstruct);
+  linear_algebra_instruction->SetInOperand(0, {result_column_ids[0]});
+  for (uint32_t i = 1; i < result_column_ids.size(); i++) {
+    linear_algebra_instruction->AddOperand(
+        {SPV_OPERAND_TYPE_ID, {result_column_ids[i]}});
+  }
+
+  fuzzerutil::UpdateModuleIdBound(
+      ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1));
+}
+
 void TransformationReplaceLinearAlgebraInstruction::ReplaceOpVectorTimesScalar(
     opt::IRContext* ir_context,
     opt::Instruction* linear_algebra_instruction) const {
@@ -740,6 +850,92 @@
       ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1));
 }
 
+void TransformationReplaceLinearAlgebraInstruction::ReplaceOpOuterProduct(
+    opt::IRContext* ir_context,
+    opt::Instruction* linear_algebra_instruction) const {
+  // Gets vector 1 information.
+  auto vector_1_instruction = ir_context->get_def_use_mgr()->GetDef(
+      linear_algebra_instruction->GetSingleWordInOperand(0));
+  uint32_t vector_1_component_count =
+      ir_context->get_type_mgr()
+          ->GetType(vector_1_instruction->type_id())
+          ->AsVector()
+          ->element_count();
+  auto vector_1_component_type = ir_context->get_type_mgr()
+                                     ->GetType(vector_1_instruction->type_id())
+                                     ->AsVector()
+                                     ->element_type();
+
+  // Gets vector 2 information.
+  auto vector_2_instruction = ir_context->get_def_use_mgr()->GetDef(
+      linear_algebra_instruction->GetSingleWordInOperand(1));
+  uint32_t vector_2_component_count =
+      ir_context->get_type_mgr()
+          ->GetType(vector_2_instruction->type_id())
+          ->AsVector()
+          ->element_count();
+
+  uint32_t fresh_id_index = 0;
+  std::vector<uint32_t> result_column_ids(vector_2_component_count);
+  for (uint32_t i = 0; i < vector_2_component_count; i++) {
+    // Extracts |vector_2| component.
+    uint32_t vector_2_component_id = message_.fresh_ids(fresh_id_index++);
+    linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpCompositeExtract,
+        ir_context->get_type_mgr()->GetId(vector_1_component_type),
+        vector_2_component_id,
+        opt::Instruction::OperandList(
+            {{SPV_OPERAND_TYPE_ID, {vector_2_instruction->result_id()}},
+             {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}})));
+
+    std::vector<uint32_t> column_component_ids(vector_1_component_count);
+    for (uint32_t j = 0; j < vector_1_component_count; j++) {
+      // Extracts |vector_1| component.
+      uint32_t vector_1_component_id = message_.fresh_ids(fresh_id_index++);
+      linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpCompositeExtract,
+          ir_context->get_type_mgr()->GetId(vector_1_component_type),
+          vector_1_component_id,
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {vector_1_instruction->result_id()}},
+               {SPV_OPERAND_TYPE_LITERAL_INTEGER, {j}}})));
+
+      // Multiplies |vector_1| and |vector_2| components.
+      column_component_ids[j] = message_.fresh_ids(fresh_id_index++);
+      linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpFMul,
+          ir_context->get_type_mgr()->GetId(vector_1_component_type),
+          column_component_ids[j],
+          opt::Instruction::OperandList(
+              {{SPV_OPERAND_TYPE_ID, {vector_2_component_id}},
+               {SPV_OPERAND_TYPE_ID, {vector_1_component_id}}})));
+    }
+
+    // Inserts the resulting matrix column.
+    opt::Instruction::OperandList in_operands;
+    for (auto& column_component_id : column_component_ids) {
+      in_operands.push_back({SPV_OPERAND_TYPE_ID, {column_component_id}});
+    }
+    result_column_ids[i] = message_.fresh_ids(fresh_id_index++);
+    linear_algebra_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+        ir_context, SpvOpCompositeConstruct, vector_1_instruction->type_id(),
+        result_column_ids[i], in_operands));
+  }
+
+  // The OpOuterProduct instruction is changed to an OpCompositeConstruct
+  // instruction.
+  linear_algebra_instruction->SetOpcode(SpvOpCompositeConstruct);
+  linear_algebra_instruction->SetInOperand(0, {result_column_ids[0]});
+  linear_algebra_instruction->SetInOperand(1, {result_column_ids[1]});
+  for (uint32_t i = 2; i < result_column_ids.size(); i++) {
+    linear_algebra_instruction->AddOperand(
+        {SPV_OPERAND_TYPE_ID, {result_column_ids[i]}});
+  }
+
+  fuzzerutil::UpdateModuleIdBound(
+      ir_context, message_.fresh_ids(message_.fresh_ids().size() - 1));
+}
+
 void TransformationReplaceLinearAlgebraInstruction::ReplaceOpDot(
     opt::IRContext* ir_context,
     opt::Instruction* linear_algebra_instruction) const {
@@ -832,5 +1028,14 @@
   }
 }
 
+std::unordered_set<uint32_t>
+TransformationReplaceLinearAlgebraInstruction::GetFreshIds() const {
+  std::unordered_set<uint32_t> result;
+  for (auto id : message_.fresh_ids()) {
+    result.insert(id);
+  }
+  return result;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_linear_algebra_instruction.h b/source/fuzz/transformation_replace_linear_algebra_instruction.h
index 530c1f2..45f4aa6 100644
--- a/source/fuzz/transformation_replace_linear_algebra_instruction.h
+++ b/source/fuzz/transformation_replace_linear_algebra_instruction.h
@@ -43,6 +43,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns the number of ids needed to apply the transformation.
@@ -52,6 +54,10 @@
  private:
   protobufs::TransformationReplaceLinearAlgebraInstruction message_;
 
+  // Replaces an OpTranspose instruction.
+  void ReplaceOpTranspose(opt::IRContext* ir_context,
+                          opt::Instruction* instruction) const;
+
   // Replaces an OpVectorTimesScalar instruction.
   void ReplaceOpVectorTimesScalar(opt::IRContext* ir_context,
                                   opt::Instruction* instruction) const;
@@ -72,6 +78,10 @@
   void ReplaceOpMatrixTimesMatrix(opt::IRContext* ir_context,
                                   opt::Instruction* instruction) const;
 
+  // Replaces an OpOuterProduct instruction.
+  void ReplaceOpOuterProduct(opt::IRContext* ir_context,
+                             opt::Instruction* instruction) const;
+
   // Replaces an OpDot instruction.
   void ReplaceOpDot(opt::IRContext* ir_context,
                     opt::Instruction* instruction) const;
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 52964bf..6067fca 100644
--- a/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp
+++ b/source/fuzz/transformation_replace_load_store_with_copy_memory.cpp
@@ -45,26 +45,18 @@
     opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // This transformation is only applicable to the pair of OpLoad and OpStore
   // instructions.
-  if (message_.load_instruction_descriptor().target_instruction_opcode() !=
-      SpvOpLoad) {
-    return false;
-  }
-  if (message_.store_instruction_descriptor().target_instruction_opcode() !=
-      SpvOpStore) {
-    return false;
-  }
 
   // The OpLoad instruction must be defined.
   auto load_instruction =
       FindInstruction(message_.load_instruction_descriptor(), ir_context);
-  if (!load_instruction) {
+  if (!load_instruction || load_instruction->opcode() != SpvOpLoad) {
     return false;
   }
 
   // The OpStore instruction must be defined.
   auto store_instruction =
       FindInstruction(message_.store_instruction_descriptor(), ir_context);
-  if (!store_instruction) {
+  if (!store_instruction || store_instruction->opcode() != SpvOpStore) {
     return false;
   }
 
@@ -188,5 +180,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationReplaceLoadStoreWithCopyMemory::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
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 0fb9a09..4dd728e 100644
--- a/source/fuzz/transformation_replace_load_store_with_copy_memory.h
+++ b/source/fuzz/transformation_replace_load_store_with_copy_memory.h
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef SPIRV_TOOLS_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H
-#define SPIRV_TOOLS_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H_
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
@@ -64,6 +64,8 @@
   static bool IsStorageClassSafeAcrossMemoryBarriers(
       SpvStorageClass storage_class);
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
  private:
@@ -73,4 +75,4 @@
 }  // namespace fuzz
 }  // namespace spvtools
 
-#endif  // SPIRV_TOOLS_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_LOAD_STORE_WITH_COPY_MEMORY_H_
diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp
new file mode 100644
index 0000000..f13af7e
--- /dev/null
+++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp
@@ -0,0 +1,115 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationReplaceOpPhiIdFromDeadPredecessor::
+    TransformationReplaceOpPhiIdFromDeadPredecessor(
+        const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor&
+            message)
+    : message_(message) {}
+
+TransformationReplaceOpPhiIdFromDeadPredecessor::
+    TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id,
+                                                    uint32_t pred_label_id,
+                                                    uint32_t replacement_id) {
+  message_.set_opphi_id(opphi_id);
+  message_.set_pred_label_id(pred_label_id);
+  message_.set_replacement_id(replacement_id);
+}
+
+bool TransformationReplaceOpPhiIdFromDeadPredecessor::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // |opphi_id| must be the id of an OpPhi instruction.
+  auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id());
+  if (!opphi_def || opphi_def->opcode() != SpvOpPhi) {
+    return false;
+  }
+
+  // |pred_label_id| must be the label id of a dead block.
+  auto pred_block = ir_context->get_instr_block(message_.pred_label_id());
+  if (!pred_block || pred_block->id() != message_.pred_label_id() ||
+      !transformation_context.GetFactManager()->BlockIsDead(pred_block->id())) {
+    return false;
+  }
+
+  // |pred_label_id| must be one of the predecessors of the block containing the
+  // OpPhi instruction.
+  bool found = false;
+  for (auto pred :
+       ir_context->cfg()->preds(ir_context->get_instr_block(opphi_def)->id())) {
+    if (pred == message_.pred_label_id()) {
+      found = true;
+      break;
+    }
+  }
+
+  if (!found) {
+    return false;
+  }
+
+  // |replacement_id| must have the same type id as the OpPhi instruction.
+  auto replacement_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.replacement_id());
+
+  if (!replacement_def || replacement_def->type_id() != opphi_def->type_id()) {
+    return false;
+  }
+
+  // The replacement id must be available at the end of the predecessor.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(
+      ir_context, pred_block->terminator(), replacement_def->result_id());
+}
+
+void TransformationReplaceOpPhiIdFromDeadPredecessor::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* /* transformation_context */) const {
+  // Get the OpPhi instruction.
+  auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id());
+
+  // Find the index corresponding to the operand being replaced and replace it,
+  // by looping through the odd-indexed input operands and finding
+  // |pred_label_id|. The index that we are interested in is the one before
+  // that.
+  for (uint32_t i = 1; i < opphi_def->NumInOperands(); i += 2) {
+    if (opphi_def->GetSingleWordInOperand(i) == message_.pred_label_id()) {
+      // The operand to be replaced is at index i-1.
+      opphi_def->SetInOperand(i - 1, {message_.replacement_id()});
+    }
+  }
+
+  // Invalidate the analyses because we have altered the usages of ids.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation
+TransformationReplaceOpPhiIdFromDeadPredecessor::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_opphi_id_from_dead_predecessor() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t>
+TransformationReplaceOpPhiIdFromDeadPredecessor::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h
new file mode 100644
index 0000000..d26b6b0
--- /dev/null
+++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceOpPhiIdFromDeadPredecessor : public Transformation {
+ public:
+  explicit TransformationReplaceOpPhiIdFromDeadPredecessor(
+      const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor&
+          message);
+
+  TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id,
+                                                  uint32_t pred_label_id,
+                                                  uint32_t replacement_id);
+
+  // - |message_.opphi_id| is the id of an OpPhi instruction.
+  // - |message_.pred_label_id| is the label id of one of the predecessors of
+  //   the block containing the OpPhi instruction.
+  // - The predecessor has been recorded as dead.
+  // - |message_.replacement_id| is the id of an instruction with the same type
+  //   as the OpPhi instruction, available at the end of the predecessor.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the id corresponding to predecessor |message_.pred_label_id|, in
+  // the OpPhi instruction |message_.opphi_id|, with |message_.replacement_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;
+
+ private:
+  protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor message_;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_
diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp
new file mode 100644
index 0000000..7160d4d
--- /dev/null
+++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp
@@ -0,0 +1,209 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+TransformationReplaceOpSelectWithConditionalBranch::
+    TransformationReplaceOpSelectWithConditionalBranch(
+        const spvtools::fuzz::protobufs::
+            TransformationReplaceOpSelectWithConditionalBranch& message)
+    : message_(message) {}
+
+TransformationReplaceOpSelectWithConditionalBranch::
+    TransformationReplaceOpSelectWithConditionalBranch(
+        uint32_t select_id, uint32_t true_block_id, uint32_t false_block_id) {
+  message_.set_select_id(select_id);
+  message_.set_true_block_id(true_block_id);
+  message_.set_false_block_id(false_block_id);
+}
+
+bool TransformationReplaceOpSelectWithConditionalBranch::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& /* unused */) const {
+  assert((message_.true_block_id() || message_.false_block_id()) &&
+         "At least one of the ids must be non-zero.");
+
+  // Check that the non-zero ids are fresh.
+  std::set<uint32_t> used_ids;
+  for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
+    if (id && !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
+                                                            &used_ids)) {
+      return false;
+    }
+  }
+
+  auto instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.select_id());
+
+  // The instruction must exist and it must be an OpSelect instruction.
+  if (!instruction || instruction->opcode() != SpvOpSelect) {
+    return false;
+  }
+
+  // Check that the condition is a scalar boolean.
+  auto condition = ir_context->get_def_use_mgr()->GetDef(
+      instruction->GetSingleWordInOperand(0));
+  assert(condition && "The condition should always exist in a valid module.");
+
+  auto condition_type =
+      ir_context->get_type_mgr()->GetType(condition->type_id());
+  if (!condition_type->AsBool()) {
+    return false;
+  }
+
+  auto block = ir_context->get_instr_block(instruction);
+  assert(block && "The block containing the instruction must be found");
+
+  // The instruction must be the first in its block.
+  if (instruction->unique_id() != block->begin()->unique_id()) {
+    return false;
+  }
+
+  // The block must not be a merge block.
+  if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
+    return false;
+  }
+
+  // The block must have exactly one predecessor.
+  auto predecessors = ir_context->cfg()->preds(block->id());
+  if (predecessors.size() != 1) {
+    return false;
+  }
+
+  uint32_t pred_id = predecessors[0];
+  auto predecessor = ir_context->get_instr_block(pred_id);
+
+  // The predecessor must not be the header of a construct and it must end with
+  // OpBranch.
+  if (predecessor->GetMergeInst() != nullptr ||
+      predecessor->terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationReplaceOpSelectWithConditionalBranch::Apply(
+    opt::IRContext* ir_context, TransformationContext* /* unused */) const {
+  auto instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.select_id());
+
+  auto block = ir_context->get_instr_block(instruction);
+
+  auto predecessor =
+      ir_context->get_instr_block(ir_context->cfg()->preds(block->id())[0]);
+
+  // Create a new block for each non-zero id in {|message_.true_branch_id|,
+  // |message_.false_branch_id|}. Make each newly-created block branch
+  // unconditionally to the instruction block.
+  for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
+    if (id) {
+      fuzzerutil::UpdateModuleIdBound(ir_context, id);
+
+      // Create the new block.
+      auto new_block = MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpLabel, 0, id, opt::Instruction::OperandList{}));
+
+      // Add an unconditional branch from the new block to the instruction
+      // block.
+      new_block->AddInstruction(MakeUnique<opt::Instruction>(
+          ir_context, SpvOpBranch, 0, 0,
+          opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}}));
+
+      // Insert the new block right after the predecessor of the instruction
+      // block.
+      block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block);
+    }
+  }
+
+  // Delete the OpBranch instruction from the predecessor.
+  ir_context->KillInst(predecessor->terminator());
+
+  // Add an OpSelectionMerge instruction to the predecessor block, where the
+  // merge block is the instruction block.
+  predecessor->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpSelectionMerge, 0, 0,
+      opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}},
+                                    {SPV_OPERAND_TYPE_SELECTION_CONTROL,
+                                     {SpvSelectionControlMaskNone}}}));
+
+  // |if_block| will be the true block, if it has been created, the instruction
+  // block otherwise.
+  uint32_t if_block =
+      message_.true_block_id() ? message_.true_block_id() : block->id();
+
+  // |else_block| will be the false block, if it has been created, the
+  // instruction block otherwise.
+  uint32_t else_block =
+      message_.false_block_id() ? message_.false_block_id() : block->id();
+
+  assert(if_block != else_block &&
+         "|if_block| and |else_block| should always be different, if the "
+         "transformation is applicable.");
+
+  // Add a conditional branching instruction to the predecessor, branching to
+  // |if_block| if the condition is true and to |if_false| otherwise.
+  predecessor->AddInstruction(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpBranchConditional, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}},
+          {SPV_OPERAND_TYPE_ID, {if_block}},
+          {SPV_OPERAND_TYPE_ID, {else_block}}}));
+
+  // |if_pred| will be the true block, if it has been created, the existing
+  // predecessor otherwise.
+  uint32_t if_pred =
+      message_.true_block_id() ? message_.true_block_id() : predecessor->id();
+
+  // |else_pred| will be the false block, if it has been created, the existing
+  // predecessor otherwise.
+  uint32_t else_pred =
+      message_.false_block_id() ? message_.false_block_id() : predecessor->id();
+
+  // Replace the OpSelect instruction in the merge block with an OpPhi.
+  // This:          OpSelect %type %cond %if %else
+  // will become:   OpPhi %type %if %if_pred %else %else_pred
+  instruction->SetOpcode(SpvOpPhi);
+  std::vector<opt::Operand> operands;
+
+  operands.emplace_back(instruction->GetInOperand(1));
+  operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}});
+
+  operands.emplace_back(instruction->GetInOperand(2));
+  operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}});
+
+  instruction->SetInOperands(std::move(operands));
+
+  // Invalidate all analyses, since the structure of the module was changed.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation
+TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_opselect_with_conditional_branch() = message_;
+  return result;
+}
+
+std::unordered_set<uint32_t>
+TransformationReplaceOpSelectWithConditionalBranch::GetFreshIds() const {
+  return {message_.true_block_id(), message_.false_block_id()};
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.h b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h
new file mode 100644
index 0000000..8ee5c7f
--- /dev/null
+++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceOpSelectWithConditionalBranch
+    : public Transformation {
+ public:
+  explicit TransformationReplaceOpSelectWithConditionalBranch(
+      const protobufs::TransformationReplaceOpSelectWithConditionalBranch&
+          message);
+
+  TransformationReplaceOpSelectWithConditionalBranch(uint32_t select_id,
+                                                     uint32_t true_block_id,
+                                                     uint32_t false_block_id);
+
+  // - |message_.select_id| is the result id of an OpSelect instruction.
+  // - The condition of the OpSelect must be a scalar boolean.
+  // - The OpSelect instruction is the first instruction in its block.
+  // - The block containing the instruction is not a merge block, and it has a
+  //   single predecessor, which is not a header and whose last instruction is
+  //   OpBranch.
+  // - Each of |message_.true_block_id| and |message_.false_block_id| is either
+  //   0 or a valid fresh id, and at most one of them is 0. They must be
+  //   distinct.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the OpSelect instruction with id |message_.select_id| with a
+  // conditional branch and an OpPhi instruction.
+  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::TransformationReplaceOpSelectWithConditionalBranch message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
diff --git a/source/fuzz/transformation_replace_parameter_with_global.cpp b/source/fuzz/transformation_replace_parameter_with_global.cpp
index f680b63..cdf7645 100644
--- a/source/fuzz/transformation_replace_parameter_with_global.cpp
+++ b/source/fuzz/transformation_replace_parameter_with_global.cpp
@@ -57,10 +57,7 @@
   // |parameter_id|.
 
   // Check that replaced parameter has valid type.
-  const auto* param_type =
-      ir_context->get_type_mgr()->GetType(param_inst->type_id());
-  assert(param_type && "Parameter has invalid type");
-  if (!IsParameterTypeSupported(*param_type)) {
+  if (!IsParameterTypeSupported(ir_context, param_inst->type_id())) {
     return false;
   }
 
@@ -99,14 +96,6 @@
       fuzzerutil::MaybeGetZeroConstant(ir_context, *transformation_context,
                                        param_inst->type_id(), false));
 
-  // Mark the global variable's pointee as irrelevant if replaced parameter is
-  // irrelevant.
-  if (transformation_context->GetFactManager()->IdIsIrrelevant(
-          message_.parameter_id())) {
-    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
-        message_.global_variable_fresh_id());
-  }
-
   auto* function = fuzzerutil::GetFunctionFromParameterId(
       ir_context, message_.parameter_id());
   assert(function && "Function must exist");
@@ -161,7 +150,7 @@
   }
 
   // Remove the parameter from the function.
-  function->RemoveParameter(message_.parameter_id());
+  fuzzerutil::RemoveParameter(ir_context, message_.parameter_id());
 
   // Update function's type.
   {
@@ -188,6 +177,14 @@
   // Make sure our changes are analyzed
   ir_context->InvalidateAnalysesExceptFor(
       opt::IRContext::Analysis::kAnalysisNone);
+
+  // Mark the pointee of the global variable storing the parameter's value as
+  // irrelevant if replaced parameter is irrelevant.
+  if (transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.parameter_id())) {
+    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+        message_.global_variable_fresh_id());
+  }
 }
 
 protobufs::Transformation TransformationReplaceParameterWithGlobal::ToMessage()
@@ -198,26 +195,16 @@
 }
 
 bool TransformationReplaceParameterWithGlobal::IsParameterTypeSupported(
-    const opt::analysis::Type& type) {
+    opt::IRContext* ir_context, uint32_t param_type_id) {
   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
   //  Think about other type instructions we can add here.
-  switch (type.kind()) {
-    case opt::analysis::Type::kBool:
-    case opt::analysis::Type::kInteger:
-    case opt::analysis::Type::kFloat:
-    case opt::analysis::Type::kArray:
-    case opt::analysis::Type::kMatrix:
-    case opt::analysis::Type::kVector:
-      return true;
-    case opt::analysis::Type::kStruct:
-      return std::all_of(type.AsStruct()->element_types().begin(),
-                         type.AsStruct()->element_types().end(),
-                         [](const opt::analysis::Type* element_type) {
-                           return IsParameterTypeSupported(*element_type);
-                         });
-    default:
-      return false;
-  }
+  return fuzzerutil::CanCreateConstant(ir_context, param_type_id);
+}
+
+std::unordered_set<uint32_t>
+TransformationReplaceParameterWithGlobal::GetFreshIds() const {
+  return {message_.function_type_fresh_id(),
+          message_.global_variable_fresh_id()};
 }
 
 }  // namespace fuzz
diff --git a/source/fuzz/transformation_replace_parameter_with_global.h b/source/fuzz/transformation_replace_parameter_with_global.h
index 49e7585..c2d5f8f 100644
--- a/source/fuzz/transformation_replace_parameter_with_global.h
+++ b/source/fuzz/transformation_replace_parameter_with_global.h
@@ -51,11 +51,14 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if the type of the parameter is supported by this
   // transformation.
-  static bool IsParameterTypeSupported(const opt::analysis::Type& type);
+  static bool IsParameterTypeSupported(opt::IRContext* ir_context,
+                                       uint32_t param_type_id);
 
  private:
   protobufs::TransformationReplaceParameterWithGlobal message_;
diff --git a/source/fuzz/transformation_replace_params_with_struct.cpp b/source/fuzz/transformation_replace_params_with_struct.cpp
index a2aaa3a..0a135e5 100644
--- a/source/fuzz/transformation_replace_params_with_struct.cpp
+++ b/source/fuzz/transformation_replace_params_with_struct.cpp
@@ -28,8 +28,7 @@
 TransformationReplaceParamsWithStruct::TransformationReplaceParamsWithStruct(
     const std::vector<uint32_t>& parameter_id, uint32_t fresh_function_type_id,
     uint32_t fresh_parameter_id,
-    const std::unordered_map<uint32_t, uint32_t>&
-        caller_id_to_fresh_composite_id) {
+    const std::map<uint32_t, uint32_t>& caller_id_to_fresh_composite_id) {
   message_.set_fresh_function_type_id(fresh_function_type_id);
   message_.set_fresh_parameter_id(fresh_parameter_id);
 
@@ -37,9 +36,8 @@
     message_.add_parameter_id(id);
   }
 
-  message_.mutable_caller_id_to_fresh_composite_id()->insert(
-      caller_id_to_fresh_composite_id.begin(),
-      caller_id_to_fresh_composite_id.end());
+  *message_.mutable_caller_id_to_fresh_composite_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(caller_id_to_fresh_composite_id);
 }
 
 bool TransformationReplaceParamsWithStruct::IsApplicable(
@@ -87,10 +85,8 @@
     }
 
     // Check that the parameter with result id |id| has supported type.
-    const auto* type = ir_context->get_type_mgr()->GetType(
-        fuzzerutil::GetTypeId(ir_context, id));
-    assert(type && "Parameter has invalid type");
-    if (!IsParameterTypeSupported(*type)) {
+    if (!IsParameterTypeSupported(ir_context,
+                                  fuzzerutil::GetTypeId(ir_context, id))) {
       return false;
     }
   }
@@ -103,13 +99,16 @@
     return false;
   }
 
+  const auto caller_id_to_fresh_composite_id =
+      fuzzerutil::RepeatedUInt32PairToMap(
+          message_.caller_id_to_fresh_composite_id());
+
   // Check that |callee_id_to_fresh_composite_id| is valid.
   for (const auto* inst :
        fuzzerutil::GetCallers(ir_context, function->result_id())) {
     // Check that the callee is present in the map. It's ok if the map contains
     // more ids that there are callees (those ids will not be used).
-    if (!message_.caller_id_to_fresh_composite_id().contains(
-            inst->result_id())) {
+    if (!caller_id_to_fresh_composite_id.count(inst->result_id())) {
       return false;
     }
   }
@@ -118,7 +117,7 @@
   std::vector<uint32_t> fresh_ids = {message_.fresh_function_type_id(),
                                      message_.fresh_parameter_id()};
 
-  for (const auto& entry : message_.caller_id_to_fresh_composite_id()) {
+  for (const auto& entry : caller_id_to_fresh_composite_id) {
     fresh_ids.push_back(entry.second);
   }
 
@@ -151,21 +150,12 @@
   // Compute indices of replaced parameters. This will be used to adjust
   // OpFunctionCall instructions and create OpCompositeConstruct instructions at
   // every call site.
-  std::vector<uint32_t> indices_of_replaced_params;
-  {
-    // We want to destroy |params| after the loop because it will contain
-    // dangling pointers when we remove parameters from the function.
-    auto params = fuzzerutil::GetParameters(ir_context, function->result_id());
-    for (auto id : message_.parameter_id()) {
-      auto it = std::find_if(params.begin(), params.end(),
-                             [id](const opt::Instruction* param) {
-                               return param->result_id() == id;
-                             });
-      assert(it != params.end() && "Parameter's id is invalid");
-      indices_of_replaced_params.push_back(
-          static_cast<uint32_t>(it - params.begin()));
-    }
-  }
+  const auto indices_of_replaced_params =
+      ComputeIndicesOfReplacedParameters(ir_context);
+
+  const auto caller_id_to_fresh_composite_id =
+      fuzzerutil::RepeatedUInt32PairToMap(
+          message_.caller_id_to_fresh_composite_id());
 
   // Update all function calls.
   for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
@@ -179,17 +169,18 @@
     }
 
     // Remove arguments from the function call. We do it in a separate loop
-    // and in reverse order to make sure we have removed correct operands.
-    for (auto it = indices_of_replaced_params.rbegin();
-         it != indices_of_replaced_params.rend(); ++it) {
+    // and in decreasing order to make sure we have removed correct operands.
+    for (auto index : std::set<uint32_t, std::greater<uint32_t>>(
+             indices_of_replaced_params.begin(),
+             indices_of_replaced_params.end())) {
       // +1 since the first in operand to OpFunctionCall is the result id of
       // the function.
-      inst->RemoveInOperand(*it + 1);
+      inst->RemoveInOperand(index + 1);
     }
 
     // Insert OpCompositeConstruct before the function call.
     auto fresh_composite_id =
-        message_.caller_id_to_fresh_composite_id().at(inst->result_id());
+        caller_id_to_fresh_composite_id.at(inst->result_id());
     inst->InsertBefore(MakeUnique<opt::Instruction>(
         ir_context, SpvOpCompositeConstruct, struct_type_id, fresh_composite_id,
         std::move(composite_components)));
@@ -227,7 +218,7 @@
             {SPV_OPERAND_TYPE_ID, {message_.fresh_parameter_id()}},
             {SPV_OPERAND_TYPE_LITERAL_INTEGER, {static_cast<uint32_t>(i)}}}));
 
-    function->RemoveParameter(param_inst->result_id());
+    fuzzerutil::RemoveParameter(ir_context, param_inst->result_id());
   }
 
   // Update function's type.
@@ -270,26 +261,10 @@
 }
 
 bool TransformationReplaceParamsWithStruct::IsParameterTypeSupported(
-    const opt::analysis::Type& param_type) {
+    opt::IRContext* ir_context, uint32_t param_type_id) {
   // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
   //  Consider adding support for more types of parameters.
-  switch (param_type.kind()) {
-    case opt::analysis::Type::kBool:
-    case opt::analysis::Type::kInteger:
-    case opt::analysis::Type::kFloat:
-    case opt::analysis::Type::kArray:
-    case opt::analysis::Type::kVector:
-    case opt::analysis::Type::kMatrix:
-      return true;
-    case opt::analysis::Type::kStruct:
-      return std::all_of(param_type.AsStruct()->element_types().begin(),
-                         param_type.AsStruct()->element_types().end(),
-                         [](const opt::analysis::Type* type) {
-                           return IsParameterTypeSupported(*type);
-                         });
-    default:
-      return false;
-  }
+  return fuzzerutil::CanCreateConstant(ir_context, param_type_id);
 }
 
 uint32_t TransformationReplaceParamsWithStruct::MaybeGetRequiredStructType(
@@ -302,5 +277,40 @@
   return fuzzerutil::MaybeGetStructType(ir_context, component_type_ids);
 }
 
+std::vector<uint32_t>
+TransformationReplaceParamsWithStruct::ComputeIndicesOfReplacedParameters(
+    opt::IRContext* ir_context) const {
+  assert(!message_.parameter_id().empty() &&
+         "There must be at least one parameter to replace");
+
+  const auto* function = fuzzerutil::GetFunctionFromParameterId(
+      ir_context, message_.parameter_id(0));
+  assert(function && "|parameter_id|s are invalid");
+
+  std::vector<uint32_t> result;
+
+  auto params = fuzzerutil::GetParameters(ir_context, function->result_id());
+  for (auto id : message_.parameter_id()) {
+    auto it = std::find_if(params.begin(), params.end(),
+                           [id](const opt::Instruction* param) {
+                             return param->result_id() == id;
+                           });
+    assert(it != params.end() && "Parameter's id is invalid");
+    result.push_back(static_cast<uint32_t>(it - params.begin()));
+  }
+
+  return result;
+}
+
+std::unordered_set<uint32_t>
+TransformationReplaceParamsWithStruct::GetFreshIds() const {
+  std::unordered_set<uint32_t> result = {message_.fresh_function_type_id(),
+                                         message_.fresh_parameter_id()};
+  for (auto& pair : message_.caller_id_to_fresh_composite_id()) {
+    result.insert(pair.second());
+  }
+  return result;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_params_with_struct.h b/source/fuzz/transformation_replace_params_with_struct.h
index 0ff7340..afa6b14 100644
--- a/source/fuzz/transformation_replace_params_with_struct.h
+++ b/source/fuzz/transformation_replace_params_with_struct.h
@@ -15,7 +15,7 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_REPLACE_PARAMS_WITH_STRUCT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_REPLACE_PARAMS_WITH_STRUCT_H_
 
-#include <unordered_map>
+#include <map>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
@@ -33,8 +33,7 @@
   TransformationReplaceParamsWithStruct(
       const std::vector<uint32_t>& parameter_id,
       uint32_t fresh_function_type_id, uint32_t fresh_parameter_id,
-      const std::unordered_map<uint32_t, uint32_t>&
-          caller_id_to_fresh_composite_id);
+      const std::map<uint32_t, uint32_t>& caller_id_to_fresh_composite_id);
 
   // - Each element of |parameter_id| is a valid result id of some
   //   OpFunctionParameter instruction. All parameter ids must correspond to
@@ -64,16 +63,25 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Returns true if parameter's type is supported by this transformation.
-  static bool IsParameterTypeSupported(const opt::analysis::Type& param_type);
+  static bool IsParameterTypeSupported(opt::IRContext* ir_context,
+                                       uint32_t param_type_id);
 
  private:
   // Returns a result id of the OpTypeStruct instruction required by this
   // transformation (see docs on the IsApplicable method to learn more).
   uint32_t MaybeGetRequiredStructType(opt::IRContext* ir_context) const;
 
+  // Returns a vector of indices of parameters to replace. Concretely, i'th
+  // element is the index of the parameter with result id |parameter_id[i]| in
+  // its function.
+  std::vector<uint32_t> ComputeIndicesOfReplacedParameters(
+      opt::IRContext* ir_context) const;
+
   protobufs::TransformationReplaceParamsWithStruct message_;
 };
 
diff --git a/source/fuzz/transformation_set_function_control.cpp b/source/fuzz/transformation_set_function_control.cpp
index d01e743..8ab9b8c 100644
--- a/source/fuzz/transformation_set_function_control.cpp
+++ b/source/fuzz/transformation_set_function_control.cpp
@@ -96,5 +96,10 @@
   return nullptr;
 }
 
+std::unordered_set<uint32_t> TransformationSetFunctionControl::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_set_function_control.h b/source/fuzz/transformation_set_function_control.h
index 5109f74..2952cc6 100644
--- a/source/fuzz/transformation_set_function_control.h
+++ b/source/fuzz/transformation_set_function_control.h
@@ -46,6 +46,8 @@
   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:
diff --git a/source/fuzz/transformation_set_loop_control.cpp b/source/fuzz/transformation_set_loop_control.cpp
index 845ac69..b2180d8 100644
--- a/source/fuzz/transformation_set_loop_control.cpp
+++ b/source/fuzz/transformation_set_loop_control.cpp
@@ -42,8 +42,8 @@
     return false;
   }
 
-  // We sanity-check that the transformation does not try to set any meaningless
-  // bits of the loop control mask.
+  // We assert that the transformation does not try to set any meaningless bits
+  // of the loop control mask.
   uint32_t all_loop_control_mask_bits_set =
       SpvLoopControlUnrollMask | SpvLoopControlDontUnrollMask |
       SpvLoopControlDependencyInfiniteMask |
@@ -213,5 +213,9 @@
   }
 }
 
+std::unordered_set<uint32_t> TransformationSetLoopControl::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_set_loop_control.h b/source/fuzz/transformation_set_loop_control.h
index f0c364f..c3480b1 100644
--- a/source/fuzz/transformation_set_loop_control.h
+++ b/source/fuzz/transformation_set_loop_control.h
@@ -56,6 +56,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Does the version of SPIR-V being used support the PartialCount loop
diff --git a/source/fuzz/transformation_set_memory_operands_mask.cpp b/source/fuzz/transformation_set_memory_operands_mask.cpp
index f52f170..deb207a 100644
--- a/source/fuzz/transformation_set_memory_operands_mask.cpp
+++ b/source/fuzz/transformation_set_memory_operands_mask.cpp
@@ -219,5 +219,10 @@
   }
 }
 
+std::unordered_set<uint32_t> TransformationSetMemoryOperandsMask::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_set_memory_operands_mask.h b/source/fuzz/transformation_set_memory_operands_mask.h
index 9f5081b..7357b1a 100644
--- a/source/fuzz/transformation_set_memory_operands_mask.h
+++ b/source/fuzz/transformation_set_memory_operands_mask.h
@@ -51,6 +51,8 @@
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
   protobufs::Transformation ToMessage() const override;
 
   // Helper function that determines whether |instruction| is a memory
diff --git a/source/fuzz/transformation_set_selection_control.cpp b/source/fuzz/transformation_set_selection_control.cpp
index bee1e35..625187e 100644
--- a/source/fuzz/transformation_set_selection_control.cpp
+++ b/source/fuzz/transformation_set_selection_control.cpp
@@ -56,5 +56,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationSetSelectionControl::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_set_selection_control.h b/source/fuzz/transformation_set_selection_control.h
index 21fbdda..56b5885 100644
--- a/source/fuzz/transformation_set_selection_control.h
+++ b/source/fuzz/transformation_set_selection_control.h
@@ -44,6 +44,8 @@
   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:
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
index b020d98..b383c40 100644
--- a/source/fuzz/transformation_split_block.cpp
+++ b/source/fuzz/transformation_split_block.cpp
@@ -83,27 +83,9 @@
 
   // Splitting the block must not separate the definition of an OpSampledImage
   // from its use: the SPIR-V data rules require them to be in the same block.
-  std::set<uint32_t> sampled_image_result_ids;
-  bool before_split = true;
-  for (auto& instruction : *block_to_split) {
-    if (&instruction == &*split_before) {
-      before_split = false;
-    }
-    if (before_split) {
-      if (instruction.opcode() == SpvOpSampledImage) {
-        sampled_image_result_ids.insert(instruction.result_id());
-      }
-    } else {
-      if (!instruction.WhileEachInId(
-              [&sampled_image_result_ids](uint32_t* id) -> bool {
-                return !sampled_image_result_ids.count(*id);
-              })) {
-        return false;
-      }
-    }
-  }
-
-  return true;
+  return !fuzzerutil::
+      SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+          block_to_split, instruction_to_split_before);
 }
 
 void TransformationSplitBlock::Apply(
@@ -135,14 +117,17 @@
   // 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) {
-    // The following assertion is a sanity check.  It is guaranteed to hold
-    // if IsApplicable holds.
-    assert(phi_inst->NumInOperands() == 2 &&
-           "We can only split a block before an OpPhi if block has exactly "
-           "one predecessor.");
+    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()});
   });
 
+  // Invalidate all analyses
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+
   // If the block being split was dead, the new block arising from the split is
   // also dead.
   if (transformation_context->GetFactManager()->BlockIsDead(
@@ -150,10 +135,6 @@
     transformation_context->GetFactManager()->AddFactBlockIsDead(
         message_.fresh_id());
   }
-
-  // Invalidate all analyses
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 protobufs::Transformation TransformationSplitBlock::ToMessage() const {
@@ -162,5 +143,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationSplitBlock::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_split_block.h b/source/fuzz/transformation_split_block.h
index 3bf6dfd..27bf6f8 100644
--- a/source/fuzz/transformation_split_block.h
+++ b/source/fuzz/transformation_split_block.h
@@ -53,6 +53,8 @@
   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:
diff --git a/source/fuzz/transformation_store.cpp b/source/fuzz/transformation_store.cpp
index f77afe3..460ca01 100644
--- a/source/fuzz/transformation_store.cpp
+++ b/source/fuzz/transformation_store.cpp
@@ -125,5 +125,9 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationStore::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_store.h b/source/fuzz/transformation_store.h
index 6746aab..7052048 100644
--- a/source/fuzz/transformation_store.h
+++ b/source/fuzz/transformation_store.h
@@ -53,6 +53,8 @@
   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:
diff --git a/source/fuzz/transformation_swap_commutable_operands.cpp b/source/fuzz/transformation_swap_commutable_operands.cpp
index b7622a2..b8bdb79 100644
--- a/source/fuzz/transformation_swap_commutable_operands.cpp
+++ b/source/fuzz/transformation_swap_commutable_operands.cpp
@@ -31,8 +31,7 @@
 }
 
 bool TransformationSwapCommutableOperands::IsApplicable(
-    opt::IRContext* ir_context, const TransformationContext& /*unused*/
-    ) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   auto instruction =
       FindInstruction(message_.instruction_descriptor(), ir_context);
   if (instruction == nullptr) return false;
@@ -46,8 +45,7 @@
 }
 
 void TransformationSwapCommutableOperands::Apply(
-    opt::IRContext* ir_context, TransformationContext* /*unused*/
-    ) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   auto instruction =
       FindInstruction(message_.instruction_descriptor(), ir_context);
   // By design, the instructions defined to be commutative have exactly two
@@ -62,5 +60,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t> TransformationSwapCommutableOperands::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_swap_commutable_operands.h b/source/fuzz/transformation_swap_commutable_operands.h
index 7fe5b70..c291c3e 100644
--- a/source/fuzz/transformation_swap_commutable_operands.h
+++ b/source/fuzz/transformation_swap_commutable_operands.h
@@ -41,6 +41,8 @@
   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:
diff --git a/source/fuzz/transformation_swap_conditional_branch_operands.cpp b/source/fuzz/transformation_swap_conditional_branch_operands.cpp
index 25f48c4..866fee6 100644
--- a/source/fuzz/transformation_swap_conditional_branch_operands.cpp
+++ b/source/fuzz/transformation_swap_conditional_branch_operands.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_swap_conditional_branch_operands.h"
+
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 
@@ -100,5 +101,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationSwapConditionalBranchOperands::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_swap_conditional_branch_operands.h b/source/fuzz/transformation_swap_conditional_branch_operands.h
index dd68ff8..022c54a 100644
--- a/source/fuzz/transformation_swap_conditional_branch_operands.h
+++ b/source/fuzz/transformation_swap_conditional_branch_operands.h
@@ -46,6 +46,8 @@
   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:
diff --git a/source/fuzz/transformation_toggle_access_chain_instruction.cpp b/source/fuzz/transformation_toggle_access_chain_instruction.cpp
index ca24a18..1952a34 100644
--- a/source/fuzz/transformation_toggle_access_chain_instruction.cpp
+++ b/source/fuzz/transformation_toggle_access_chain_instruction.cpp
@@ -33,8 +33,7 @@
 }
 
 bool TransformationToggleAccessChainInstruction::IsApplicable(
-    opt::IRContext* ir_context, const TransformationContext& /*unused*/
-    ) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   auto instruction =
       FindInstruction(message_.instruction_descriptor(), ir_context);
   if (instruction == nullptr) {
@@ -56,8 +55,7 @@
 }
 
 void TransformationToggleAccessChainInstruction::Apply(
-    opt::IRContext* ir_context, TransformationContext* /*unused*/
-    ) const {
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
   auto instruction =
       FindInstruction(message_.instruction_descriptor(), ir_context);
   SpvOp opcode = instruction->opcode();
@@ -79,5 +77,10 @@
   return result;
 }
 
+std::unordered_set<uint32_t>
+TransformationToggleAccessChainInstruction::GetFreshIds() const {
+  return std::unordered_set<uint32_t>();
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_toggle_access_chain_instruction.h b/source/fuzz/transformation_toggle_access_chain_instruction.h
index 9cd8fd6..977b0d7 100644
--- a/source/fuzz/transformation_toggle_access_chain_instruction.h
+++ b/source/fuzz/transformation_toggle_access_chain_instruction.h
@@ -41,6 +41,8 @@
   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:
diff --git a/source/fuzz/transformation_vector_shuffle.cpp b/source/fuzz/transformation_vector_shuffle.cpp
index b3bd593..05af18e 100644
--- a/source/fuzz/transformation_vector_shuffle.cpp
+++ b/source/fuzz/transformation_vector_shuffle.cpp
@@ -39,8 +39,7 @@
 }
 
 bool TransformationVectorShuffle::IsApplicable(
-    opt::IRContext* ir_context,
-    const TransformationContext& transformation_context) const {
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
   // The fresh id must not already be in use.
   if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
     return false;
@@ -57,26 +56,12 @@
   if (!vector1_instruction || !vector1_instruction->type_id()) {
     return false;
   }
-  // We should be able to create a synonym of |vector1| if it's not irrelevant.
-  if (!transformation_context.GetFactManager()->IdIsIrrelevant(
-          message_.vector1()) &&
-      !fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
-                                    vector1_instruction)) {
-    return false;
-  }
   // The second vector must be an instruction with a type id
   auto vector2_instruction =
       ir_context->get_def_use_mgr()->GetDef(message_.vector2());
   if (!vector2_instruction || !vector2_instruction->type_id()) {
     return false;
   }
-  // We should be able to create a synonym of |vector2| if it's not irrelevant.
-  if (!transformation_context.GetFactManager()->IdIsIrrelevant(
-          message_.vector2()) &&
-      !fuzzerutil::CanMakeSynonymOf(ir_context, transformation_context,
-                                    vector2_instruction)) {
-    return false;
-  }
   auto vector1_type =
       ir_context->get_type_mgr()->GetType(vector1_instruction->type_id());
   // The first vector instruction's type must actually be a vector type.
@@ -153,61 +138,7 @@
   ir_context->InvalidateAnalysesExceptFor(
       opt::IRContext::Analysis::kAnalysisNone);
 
-  // Add synonym facts relating the defined elements of the shuffle result to
-  // the vector components that they come from.
-  for (uint32_t component_index = 0;
-       component_index < static_cast<uint32_t>(message_.component_size());
-       component_index++) {
-    uint32_t component = message_.component(component_index);
-    if (component == 0xFFFFFFFF) {
-      // This component is undefined, so move on - but first note that the
-      // overall shuffle result cannot be synonymous with any vector.
-      continue;
-    }
-
-    // This describes the element of the result vector associated with
-    // |component_index|.
-    protobufs::DataDescriptor descriptor_for_result_component =
-        MakeDataDescriptor(message_.fresh_id(), {component_index});
-
-    protobufs::DataDescriptor descriptor_for_source_component;
-
-    // Get a data descriptor for the component of the input vector to which
-    // |component| refers.
-    if (component <
-        GetVectorType(ir_context, message_.vector1())->element_count()) {
-      // Irrelevant id cannot participate in DataSynonym facts.
-      if (transformation_context->GetFactManager()->IdIsIrrelevant(
-              message_.vector1())) {
-        continue;
-      }
-
-      descriptor_for_source_component =
-          MakeDataDescriptor(message_.vector1(), {component});
-    } else {
-      // Irrelevant id cannot participate in DataSynonym facts.
-      if (transformation_context->GetFactManager()->IdIsIrrelevant(
-              message_.vector2())) {
-        continue;
-      }
-
-      auto index_into_vector_2 =
-          component -
-          GetVectorType(ir_context, message_.vector1())->element_count();
-      assert(
-          index_into_vector_2 <
-              GetVectorType(ir_context, message_.vector2())->element_count() &&
-          "Vector shuffle index is out of bounds.");
-      descriptor_for_source_component =
-          MakeDataDescriptor(message_.vector2(), {index_into_vector_2});
-    }
-
-    // Add a fact relating this input vector component with the associated
-    // result component.
-    transformation_context->GetFactManager()->AddFactDataSynonym(
-        descriptor_for_result_component, descriptor_for_source_component,
-        ir_context);
-  }
+  AddDataSynonymFacts(ir_context, transformation_context);
 }
 
 protobufs::Transformation TransformationVectorShuffle::ToMessage() const {
@@ -230,5 +161,73 @@
       ->AsVector();
 }
 
+std::unordered_set<uint32_t> TransformationVectorShuffle::GetFreshIds() const {
+  return {message_.fresh_id()};
+}
+
+void TransformationVectorShuffle::AddDataSynonymFacts(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  // If the new instruction is irrelevant (because it is in a dead block), it
+  // cannot participate in any DataSynonym fact.
+  if (transformation_context->GetFactManager()->IdIsIrrelevant(
+          message_.fresh_id())) {
+    return;
+  }
+
+  // Add synonym facts relating the defined elements of the shuffle result to
+  // the vector components that they come from.
+  for (uint32_t component_index = 0;
+       component_index < static_cast<uint32_t>(message_.component_size());
+       component_index++) {
+    uint32_t component = message_.component(component_index);
+    if (component == 0xFFFFFFFF) {
+      // This component is undefined, we do not introduce a synonym.
+      continue;
+    }
+    // This describes the element of the result vector associated with
+    // |component_index|.
+    protobufs::DataDescriptor descriptor_for_result_component =
+        MakeDataDescriptor(message_.fresh_id(), {component_index});
+
+    protobufs::DataDescriptor descriptor_for_source_component;
+
+    // Get a data descriptor for the component of the input vector to which
+    // |component| refers.
+    if (component <
+        GetVectorType(ir_context, message_.vector1())->element_count()) {
+      // 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()))) {
+        continue;
+      }
+      descriptor_for_source_component =
+          MakeDataDescriptor(message_.vector1(), {component});
+    } else {
+      // 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()))) {
+        continue;
+      }
+      auto index_into_vector_2 =
+          component -
+          GetVectorType(ir_context, message_.vector1())->element_count();
+      assert(
+          index_into_vector_2 <
+              GetVectorType(ir_context, message_.vector2())->element_count() &&
+          "Vector shuffle index is out of bounds.");
+      descriptor_for_source_component =
+          MakeDataDescriptor(message_.vector2(), {index_into_vector_2});
+    }
+
+    // Add a fact relating this input vector component with the associated
+    // result component.
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        descriptor_for_result_component, descriptor_for_source_component);
+  }
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/transformation_vector_shuffle.h b/source/fuzz/transformation_vector_shuffle.h
index f911976..cf08a62 100644
--- a/source/fuzz/transformation_vector_shuffle.h
+++ b/source/fuzz/transformation_vector_shuffle.h
@@ -52,18 +52,21 @@
   // Inserts an OpVectorShuffle instruction before
   // |message_.instruction_to_insert_before|, shuffles vectors
   // |message_.vector1| and |message_.vector2| using the indices provided by
-  // |message_.component|, into |message_.fresh_id|.  Adds a fact to the fact
-  // manager recording the fact each element of |message_.fresh_id| is
+  // |message_.component|, into |message_.fresh_id|.
+  //
+  // If |message_.fresh_id| is irrelevant (e.g. due to being in a dead block)
+  // of if one of |message_.vector1| or |message_.vector2| is irrelevant and the
+  // shuffle reads components from the irrelevant vector then no synonym facts
+  // are added.
+  //
+  // Otherwise, a fact is added recording that element of |message_.fresh_id| is
   // synonymous with the element of |message_.vector1| or |message_.vector2|
-  // from which it came (with undefined components being ignored).  If the
-  // result vector is a contiguous sub-range of one of the input vectors, a
-  // fact is added to record that |message_.fresh_id| is synonymous with this
-  // sub-range. DataSynonym facts are added only for non-irrelevant vectors
-  // (e.g. if |vector1| is irrelevant but |vector2| is not, synonyms will be
-  // created for |vector1| but not |vector2|).
+  // from which it came (with undefined components being ignored).
   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:
@@ -74,9 +77,15 @@
   uint32_t GetResultTypeId(opt::IRContext* ir_context,
                            const opt::analysis::Type& element_type) const;
 
+  // Returns the type associated with |id_of_vector| in |ir_context|.
   static opt::analysis::Vector* GetVectorType(opt::IRContext* ir_context,
                                               uint32_t id_of_vector);
 
+  // Helper method for adding data synonym facts when applying the
+  // transformation to |ir_context| and |transformation_context|.
+  void AddDataSynonymFacts(opt::IRContext* ir_context,
+                           TransformationContext* transformation_context) const;
+
   protobufs::TransformationVectorShuffle message_;
 };
 
diff --git a/source/fuzz/transformation_wrap_early_terminator_in_function.cpp b/source/fuzz/transformation_wrap_early_terminator_in_function.cpp
new file mode 100644
index 0000000..4c436f5
--- /dev/null
+++ b/source/fuzz/transformation_wrap_early_terminator_in_function.cpp
@@ -0,0 +1,183 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_early_terminator_in_function.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationWrapEarlyTerminatorInFunction::
+    TransformationWrapEarlyTerminatorInFunction(
+        const spvtools::fuzz::protobufs::
+            TransformationWrapEarlyTerminatorInFunction& message)
+    : message_(message) {}
+
+TransformationWrapEarlyTerminatorInFunction::
+    TransformationWrapEarlyTerminatorInFunction(
+        uint32_t fresh_id,
+        const protobufs::InstructionDescriptor& early_terminator_instruction,
+        uint32_t returned_value_id) {
+  message_.set_fresh_id(fresh_id);
+  *message_.mutable_early_terminator_instruction() =
+      early_terminator_instruction;
+  message_.set_returned_value_id(returned_value_id);
+}
+
+bool TransformationWrapEarlyTerminatorInFunction::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // The given id must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
+    return false;
+  }
+
+  // |message_.early_terminator_instruction| must identify an instruction, and
+  // the instruction must indeed be an early terminator.
+  auto early_terminator =
+      FindInstruction(message_.early_terminator_instruction(), ir_context);
+  if (!early_terminator) {
+    return false;
+  }
+  switch (early_terminator->opcode()) {
+    case SpvOpKill:
+    case SpvOpUnreachable:
+    case SpvOpTerminateInvocation:
+      break;
+    default:
+      return false;
+  }
+  // A wrapper function for the early terminator must exist.
+  auto wrapper_function =
+      MaybeGetWrapperFunction(ir_context, early_terminator->opcode());
+  if (wrapper_function == nullptr) {
+    return false;
+  }
+  auto enclosing_function =
+      ir_context->get_instr_block(early_terminator)->GetParent();
+  // The wrapper function cannot be the function containing the instruction we
+  // would like to wrap.
+  if (wrapper_function->result_id() == enclosing_function->result_id()) {
+    return false;
+  }
+  if (!ir_context->get_type_mgr()
+           ->GetType(enclosing_function->type_id())
+           ->AsVoid()) {
+    // The enclosing function has non-void return type.  We thus need to make
+    // sure that |message_.returned_value_instruction| provides a suitable
+    // result id to use in an OpReturnValue instruction.
+    auto returned_value_instruction =
+        ir_context->get_def_use_mgr()->GetDef(message_.returned_value_id());
+    if (!returned_value_instruction || !returned_value_instruction->type_id() ||
+        returned_value_instruction->type_id() !=
+            enclosing_function->type_id()) {
+      return false;
+    }
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(
+            ir_context, early_terminator, message_.returned_value_id())) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void TransformationWrapEarlyTerminatorInFunction::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  auto early_terminator =
+      FindInstruction(message_.early_terminator_instruction(), ir_context);
+  auto enclosing_block = ir_context->get_instr_block(early_terminator);
+  auto enclosing_function = enclosing_block->GetParent();
+
+  // We would like to add an OpFunctionCall before the block's terminator
+  // instruction, and then change the block's terminator to OpReturn or
+  // OpReturnValue.
+
+  // We get an iterator to the instruction we would like to insert the function
+  // call before.  It will be an iterator to the final instruction in the block
+  // unless the block is a merge block in which case it will be to the
+  // penultimate instruction (because we cannot insert an OpFunctionCall after
+  // a merge instruction).
+  auto iterator = enclosing_block->tail();
+  if (enclosing_block->MergeBlockIdIfAny()) {
+    --iterator;
+  }
+
+  auto wrapper_function =
+      MaybeGetWrapperFunction(ir_context, early_terminator->opcode());
+
+  iterator->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpFunctionCall, wrapper_function->type_id(),
+      message_.fresh_id(),
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID, {wrapper_function->result_id()}}})));
+
+  opt::Instruction::OperandList new_in_operands;
+  if (!ir_context->get_type_mgr()
+           ->GetType(enclosing_function->type_id())
+           ->AsVoid()) {
+    new_in_operands.push_back(
+        {SPV_OPERAND_TYPE_ID, {message_.returned_value_id()}});
+    early_terminator->SetOpcode(SpvOpReturnValue);
+  } else {
+    early_terminator->SetOpcode(SpvOpReturn);
+  }
+  early_terminator->SetInOperands(std::move(new_in_operands));
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+std::unordered_set<uint32_t>
+TransformationWrapEarlyTerminatorInFunction::GetFreshIds() const {
+  return std::unordered_set<uint32_t>({message_.fresh_id()});
+}
+
+protobufs::Transformation
+TransformationWrapEarlyTerminatorInFunction::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_wrap_early_terminator_in_function() = message_;
+  return result;
+}
+
+opt::Function*
+TransformationWrapEarlyTerminatorInFunction::MaybeGetWrapperFunction(
+    opt::IRContext* ir_context, SpvOp early_terminator_opcode) {
+  assert((early_terminator_opcode == SpvOpKill ||
+          early_terminator_opcode == SpvOpUnreachable ||
+          early_terminator_opcode == SpvOpTerminateInvocation) &&
+         "Invalid opcode.");
+  auto void_type_id = fuzzerutil::MaybeGetVoidType(ir_context);
+  if (!void_type_id) {
+    return nullptr;
+  }
+  auto void_function_type_id =
+      fuzzerutil::FindFunctionType(ir_context, {void_type_id});
+  if (!void_function_type_id) {
+    return nullptr;
+  }
+  for (auto& function : *ir_context->module()) {
+    if (function.DefInst().GetSingleWordInOperand(1) != void_function_type_id) {
+      continue;
+    }
+    if (function.begin()->begin()->opcode() == early_terminator_opcode) {
+      return &function;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_wrap_early_terminator_in_function.h b/source/fuzz/transformation_wrap_early_terminator_in_function.h
new file mode 100644
index 0000000..00151d4
--- /dev/null
+++ b/source/fuzz/transformation_wrap_early_terminator_in_function.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_EARLY_TERMINATOR_IN_FUNCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_WRAP_EARLY_TERMINATOR_IN_FUNCTION_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 TransformationWrapEarlyTerminatorInFunction : public Transformation {
+ public:
+  explicit TransformationWrapEarlyTerminatorInFunction(
+      const protobufs::TransformationWrapEarlyTerminatorInFunction& message);
+
+  TransformationWrapEarlyTerminatorInFunction(
+      uint32_t fresh_id,
+      const protobufs::InstructionDescriptor& early_terminator_instruction,
+      uint32_t returned_value_id);
+
+  // - |message_.fresh_id| must be fresh.
+  // - |message_.early_terminator_instruction| must identify an early terminator
+  //   instruction, i.e. an instruction with opcode OpKill, OpUnreachable or
+  //   OpTerminateInvocation.
+  // - A suitable wrapper function for the early terminator must exist, and it
+  //   must be distinct from the function containing
+  //   |message_.early_terminator_instruction|.
+  // - If the enclosing function has non-void return type then
+  //   |message_.returned_value_instruction| must be the id of an instruction of
+  //   the return type that is available at the point of the early terminator
+  //   instruction.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // An OpFunctionCall instruction to an appropriate wrapper function is
+  // inserted before |message_.early_terminator_instruction|, and
+  // |message_.early_terminator_instruction| is replaced with either OpReturn
+  // or OpReturnValue |message_.returned_value_instruction| depending on whether
+  // the enclosing function's return type is void.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  static opt::Function* MaybeGetWrapperFunction(opt::IRContext* ir_context,
+                                                SpvOp early_terminator_opcode);
+
+ private:
+  protobufs::TransformationWrapEarlyTerminatorInFunction message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_WRAP_EARLY_TERMINATOR_IN_FUNCTION_H_
diff --git a/source/fuzz/transformation_wrap_region_in_selection.cpp b/source/fuzz/transformation_wrap_region_in_selection.cpp
new file mode 100644
index 0000000..9924e36
--- /dev/null
+++ b/source/fuzz/transformation_wrap_region_in_selection.cpp
@@ -0,0 +1,166 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_region_in_selection.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
+    const protobufs::TransformationWrapRegionInSelection& message)
+    : message_(message) {}
+
+TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
+    uint32_t region_entry_block_id, uint32_t region_exit_block_id,
+    bool branch_condition) {
+  message_.set_region_entry_block_id(region_entry_block_id);
+  message_.set_region_exit_block_id(region_exit_block_id);
+  message_.set_branch_condition(branch_condition);
+}
+
+bool TransformationWrapRegionInSelection::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // Check that it is possible to outline a region of blocks without breaking
+  // domination and structured control flow rules.
+  if (!IsApplicableToBlockRange(ir_context, message_.region_entry_block_id(),
+                                message_.region_exit_block_id())) {
+    return false;
+  }
+
+  // There must exist an irrelevant boolean constant to be used as a condition
+  // in the OpBranchConditional instruction.
+  return fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
+                                          message_.branch_condition(),
+                                          true) != 0;
+}
+
+void TransformationWrapRegionInSelection::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto* new_header_block =
+      ir_context->cfg()->block(message_.region_entry_block_id());
+  assert(new_header_block->terminator()->opcode() == SpvOpBranch &&
+         "This condition should have been checked in the IsApplicable");
+
+  const auto successor_id =
+      new_header_block->terminator()->GetSingleWordInOperand(0);
+
+  // Change |entry_block|'s terminator to |OpBranchConditional|.
+  new_header_block->terminator()->SetOpcode(SpvOpBranchConditional);
+  new_header_block->terminator()->SetInOperands(
+      {{SPV_OPERAND_TYPE_ID,
+        {fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
+                                          message_.branch_condition(), true)}},
+       {SPV_OPERAND_TYPE_ID, {successor_id}},
+       {SPV_OPERAND_TYPE_ID, {successor_id}}});
+
+  // Insert OpSelectionMerge before the terminator.
+  new_header_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpSelectionMerge, 0, 0,
+      opt::Instruction::OperandList{
+          {SPV_OPERAND_TYPE_ID, {message_.region_exit_block_id()}},
+          {SPV_OPERAND_TYPE_SELECTION_CONTROL,
+           {SpvSelectionControlMaskNone}}}));
+
+  // We've change the module so we must invalidate analyses.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationWrapRegionInSelection::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_wrap_region_in_selection() = message_;
+  return result;
+}
+
+bool TransformationWrapRegionInSelection::IsApplicableToBlockRange(
+    opt::IRContext* ir_context, uint32_t header_block_candidate_id,
+    uint32_t merge_block_candidate_id) {
+  // Check that |header_block_candidate_id| and |merge_block_candidate_id| are
+  // valid.
+  const auto* header_block_candidate =
+      fuzzerutil::MaybeFindBlock(ir_context, header_block_candidate_id);
+  if (!header_block_candidate) {
+    return false;
+  }
+
+  const auto* merge_block_candidate =
+      fuzzerutil::MaybeFindBlock(ir_context, merge_block_candidate_id);
+  if (!merge_block_candidate) {
+    return false;
+  }
+
+  // |header_block_candidate| and |merge_block_candidate| must be from the same
+  // function.
+  if (header_block_candidate->GetParent() !=
+      merge_block_candidate->GetParent()) {
+    return false;
+  }
+
+  const auto* dominator_analysis =
+      ir_context->GetDominatorAnalysis(header_block_candidate->GetParent());
+  const auto* postdominator_analysis =
+      ir_context->GetPostDominatorAnalysis(header_block_candidate->GetParent());
+
+  if (!dominator_analysis->StrictlyDominates(header_block_candidate,
+                                             merge_block_candidate) ||
+      !postdominator_analysis->StrictlyDominates(merge_block_candidate,
+                                                 header_block_candidate)) {
+    return false;
+  }
+
+  // |header_block_candidate| can't be a header since we are about to make it
+  // one.
+  if (header_block_candidate->GetMergeInst()) {
+    return false;
+  }
+
+  // |header_block_candidate| must have an OpBranch terminator.
+  if (header_block_candidate->terminator()->opcode() != SpvOpBranch) {
+    return false;
+  }
+
+  // Every header block must have a unique merge block. Thus,
+  // |merge_block_candidate| can't be a merge block of some other header.
+  auto* structured_cfg = ir_context->GetStructuredCFGAnalysis();
+  if (structured_cfg->IsMergeBlock(merge_block_candidate_id)) {
+    return false;
+  }
+
+  // |header_block_candidate|'s containing construct must also contain
+  // |merge_block_candidate|.
+  //
+  // ContainingConstruct will return the id of a loop header for a block in the
+  // loop's continue construct. Thus, we must also check the case when one of
+  // the candidates is in continue construct and the other one is not.
+  if (structured_cfg->ContainingConstruct(header_block_candidate_id) !=
+          structured_cfg->ContainingConstruct(merge_block_candidate_id) ||
+      structured_cfg->IsInContinueConstruct(header_block_candidate_id) !=
+          structured_cfg->IsInContinueConstruct(merge_block_candidate_id)) {
+    return false;
+  }
+
+  return true;
+}
+
+std::unordered_set<uint32_t> TransformationWrapRegionInSelection::GetFreshIds()
+    const {
+  return std::unordered_set<uint32_t>();
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_wrap_region_in_selection.h b/source/fuzz/transformation_wrap_region_in_selection.h
new file mode 100644
index 0000000..57f4f64
--- /dev/null
+++ b/source/fuzz/transformation_wrap_region_in_selection.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_REGION_IN_SELECTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_WRAP_REGION_IN_SELECTION_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 TransformationWrapRegionInSelection : public Transformation {
+ public:
+  explicit TransformationWrapRegionInSelection(
+      const protobufs::TransformationWrapRegionInSelection& message);
+
+  TransformationWrapRegionInSelection(uint32_t region_entry_block_id,
+                                      uint32_t region_exit_block_id,
+                                      bool branch_condition);
+
+  // - It should be possible to apply this transformation to a
+  //   single-exit-single-entry region of blocks dominated by
+  //   |region_entry_block_id| and postdominated by |region_exit_block_id|
+  //   (see IsApplicableToBlockRange method for further details).
+  //
+  //   TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3828):
+  //    Consider applying this transformation to non-single-entry-single-exit
+  //    regions of blocks.
+  // - There must exist an irrelevant boolean constant with value
+  //   |branch_condition|.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // - Transforms |region_entry_block_id| into a selection header with both
+  //   branches pointing to the block's successor.
+  // - |branch_condition| is used as a condition in the header's
+  //   OpBranchConditional instruction.
+  // - Transforms |region_exit_block_id| into a merge block of the selection's
+  //   header.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns true if it's possible to apply this transformation to the
+  // single-exit-single-entry region of blocks starting with
+  // |header_block_candidate_id| and ending with |merge_block_candidate_id|.
+  // Concretely:
+  // - Both |header_block_candidate_id| and |merge_block_candidate_id| must be
+  //   result ids of some blocks in the module.
+  // - Both blocks must belong to the same function.
+  // - |header_block_candidate_id| must strictly dominate
+  //   |merge_block_candidate_id| and |merge_block_candidate_id| must strictly
+  //   postdominate |header_block_candidate_id|.
+  // - |header_block_candidate_id| can't be a header block of any construct.
+  // - |header_block_candidate_id|'s terminator must be an OpBranch.
+  // - |merge_block_candidate_id| can't be a merge block of any other construct.
+  // - Both |header_block_candidate_id| and |merge_block_candidate_id| must be
+  //   inside the same construct if any.
+  static bool IsApplicableToBlockRange(opt::IRContext* ir_context,
+                                       uint32_t header_block_candidate_id,
+                                       uint32_t merge_block_candidate_id);
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+ private:
+  protobufs::TransformationWrapRegionInSelection message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_WRAP_REGION_IN_SELECTION_H_
diff --git a/source/link/CMakeLists.txt b/source/link/CMakeLists.txt
index d308319..c8dd2f7 100644
--- a/source/link/CMakeLists.txt
+++ b/source/link/CMakeLists.txt
@@ -11,7 +11,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-add_library(SPIRV-Tools-link
+add_library(SPIRV-Tools-link ${SPIRV_TOOLS_LIBRARY_TYPE}
   linker.cpp
 )
 
diff --git a/source/name_mapper.cpp b/source/name_mapper.cpp
index 5525bbc..eb08f8f 100644
--- a/source/name_mapper.cpp
+++ b/source/name_mapper.cpp
@@ -324,7 +324,7 @@
   if (SPV_SUCCESS == grammar_.lookupOperand(type, word, &desc)) {
     return desc->name;
   } else {
-    // Invalid input.  Just give something sane.
+    // Invalid input.  Just give something.
     return std::string("StorageClass") + to_string(word);
   }
 }
diff --git a/source/opcode.cpp b/source/opcode.cpp
index f93cfd3..8305bcf 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -719,3 +719,32 @@
       return {};
   }
 }
+
+bool spvOpcodeIsAccessChain(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpAccessChain:
+    case SpvOpInBoundsAccessChain:
+    case SpvOpPtrAccessChain:
+    case SpvOpInBoundsPtrAccessChain:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool spvOpcodeIsBit(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpShiftRightLogical:
+    case SpvOpShiftRightArithmetic:
+    case SpvOpShiftLeftLogical:
+    case SpvOpBitwiseOr:
+    case SpvOpBitwiseXor:
+    case SpvOpBitwiseAnd:
+    case SpvOpNot:
+    case SpvOpBitReverse:
+    case SpvOpBitCount:
+      return true;
+    default:
+      return false;
+  }
+}
diff --git a/source/opcode.h b/source/opcode.h
index 9aeba76..3702cb3 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -134,14 +134,20 @@
 // where the order of the operands is irrelevant.
 bool spvOpcodeIsCommutativeBinaryOperator(SpvOp opcode);
 
-// Returns true for opcodes that represents linear algebra instructions.
+// Returns true for opcodes that represent linear algebra instructions.
 bool spvOpcodeIsLinearAlgebra(SpvOp opcode);
 
-// Returns true for opcodes that represents an image sample instruction.
+// Returns true for opcodes that represent image sample instructions.
 bool spvOpcodeIsImageSample(SpvOp opcode);
 
 // Returns a vector containing the indices of the memory semantics <id>
 // operands for |opcode|.
 std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
 
+// Returns true for opcodes that represent access chain instructions.
+bool spvOpcodeIsAccessChain(SpvOp opcode);
+
+// Returns true for opcodes that represent bit instructions.
+bool spvOpcodeIsBit(SpvOp opcode);
+
 #endif  // SOURCE_OPCODE_H_
diff --git a/source/operand.cpp b/source/operand.cpp
index 0910595..d4b64a8 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -208,6 +208,8 @@
     case SPV_OPERAND_TYPE_MEMORY_ACCESS:
     case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS:
       return "memory access";
+    case SPV_OPERAND_TYPE_FRAGMENT_SHADING_RATE:
+      return "shading rate";
     case SPV_OPERAND_TYPE_SCOPE_ID:
       return "scope ID";
     case SPV_OPERAND_TYPE_GROUP_OPERATION:
@@ -265,7 +267,6 @@
     case SPV_OPERAND_TYPE_NONE:
       return "NONE";
     default:
-      assert(0 && "Unhandled operand type!");
       break;
   }
   return "unknown";
@@ -361,6 +362,7 @@
     case SPV_OPERAND_TYPE_LOOP_CONTROL:
     case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
     case SPV_OPERAND_TYPE_MEMORY_ACCESS:
+    case SPV_OPERAND_TYPE_FRAGMENT_SHADING_RATE:
     case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS:
     case SPV_OPERAND_TYPE_CLDEBUG100_DEBUG_INFO_FLAGS:
       return true;
@@ -371,13 +373,35 @@
 }
 
 bool spvOperandIsOptional(spv_operand_type_t type) {
-  return SPV_OPERAND_TYPE_FIRST_OPTIONAL_TYPE <= type &&
-         type <= SPV_OPERAND_TYPE_LAST_OPTIONAL_TYPE;
+  switch (type) {
+    case SPV_OPERAND_TYPE_OPTIONAL_ID:
+    case SPV_OPERAND_TYPE_OPTIONAL_IMAGE:
+    case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS:
+    case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER:
+    case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER:
+    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_CIV:
+      return true;
+    default:
+      break;
+  }
+  // Any variable operand is also optional.
+  return spvOperandIsVariable(type);
 }
 
 bool spvOperandIsVariable(spv_operand_type_t type) {
-  return SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE <= type &&
-         type <= SPV_OPERAND_TYPE_LAST_VARIABLE_TYPE;
+  switch (type) {
+    case SPV_OPERAND_TYPE_VARIABLE_ID:
+    case SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER:
+    case SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER_ID:
+    case SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER:
+      return true;
+    default:
+      break;
+  }
+  return false;
 }
 
 bool spvExpandOperandSequenceOnce(spv_operand_type_t type,
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 0047c34..3ab43af 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -89,7 +89,6 @@
   pass.h
   pass_manager.h
   private_to_local_pass.h
-  process_lines_pass.h
   propagator.h
   reduce_load_size.h
   redundancy_elimination.h
@@ -196,7 +195,6 @@
   pass.cpp
   pass_manager.cpp
   private_to_local_pass.cpp
-  process_lines_pass.cpp
   propagator.cpp
   reduce_load_size.cpp
   redundancy_elimination.cpp
@@ -226,14 +224,14 @@
   wrap_opkill.cpp
 )
 
-if(MSVC)
+if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
   # Enable parallel builds across four cores for this lib
   add_definitions(/MP4)
 endif()
 
 spvtools_pch(SPIRV_TOOLS_OPT_SOURCES pch_source_opt)
 
-add_library(SPIRV-Tools-opt ${SPIRV_TOOLS_OPT_SOURCES})
+add_library(SPIRV-Tools-opt ${SPIRV_TOOLS_LIBRARY_TYPE} ${SPIRV_TOOLS_OPT_SOURCES})
 
 spvtools_default_compile_options(SPIRV-Tools-opt)
 target_include_directories(SPIRV-Tools-opt
@@ -245,7 +243,7 @@
 )
 # We need the assembling and disassembling functionalities in the main library.
 target_link_libraries(SPIRV-Tools-opt
-  PUBLIC ${SPIRV_TOOLS})
+  PUBLIC ${SPIRV_TOOLS_FULL_VISIBILITY})
 
 set_property(TARGET SPIRV-Tools-opt PROPERTY FOLDER "SPIRV-Tools libraries")
 spvtools_check_symbol_exports(SPIRV-Tools-opt)
diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp
index b755787..39d468f 100644
--- a/source/opt/aggressive_dead_code_elim_pass.cpp
+++ b/source/opt/aggressive_dead_code_elim_pass.cpp
@@ -22,6 +22,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/iterator.h"
 #include "source/opt/reflect.h"
 #include "source/spirv_constant.h"
@@ -532,6 +533,17 @@
       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();
   }
 
@@ -696,8 +708,8 @@
   // been marked, it is safe to remove dead global values.
   modified |= ProcessGlobalValues();
 
-  // Sanity check.
-  assert(to_kill_.size() == 0 || modified);
+  assert((to_kill_.empty() || modified) &&
+         "A dead instruction was identified, but no change recorded.");
 
   // Kill all dead instructions.
   for (auto inst : to_kill_) {
@@ -727,8 +739,8 @@
        funcIter != get_module()->end();) {
     if (live_function_set.count(&*funcIter) == 0) {
       modified = true;
-      EliminateFunction(&*funcIter);
-      funcIter = funcIter.Erase();
+      funcIter =
+          eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
     } else {
       ++funcIter;
     }
@@ -737,12 +749,6 @@
   return modified;
 }
 
-void AggressiveDCEPass::EliminateFunction(Function* func) {
-  // Remove all of the instruction in the function body
-  func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
-                    true);
-}
-
 bool AggressiveDCEPass::ProcessGlobalValues() {
   // Remove debug and annotation statements referencing dead instructions.
   // This must be done before killing the instructions, otherwise there are
@@ -950,6 +956,7 @@
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",
diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h
index 2ce5b57..f02e729 100644
--- a/source/opt/aggressive_dead_code_elim_pass.h
+++ b/source/opt/aggressive_dead_code_elim_pass.h
@@ -127,9 +127,6 @@
   // Erases functions that are unreachable from the entry points of the module.
   bool EliminateDeadFunctions();
 
-  // Removes |func| from the module and deletes all its instructions.
-  void EliminateFunction(Function* func);
-
   // For function |func|, mark all Stores to non-function-scope variables
   // and block terminating instructions as live. Recursively mark the values
   // they use. When complete, mark any non-live instructions to be deleted.
diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp
index 2de9250..d84f13f 100644
--- a/source/opt/ccp_pass.cpp
+++ b/source/opt/ccp_pass.cpp
@@ -138,6 +138,7 @@
   Instruction* folded_inst =
       context()->get_instruction_folder().FoldInstructionToConstant(instr,
                                                                     map_func);
+
   if (folded_inst != nullptr) {
     // We do not want to change the body of the function by adding new
     // instructions.  When folding we can only generate new constants.
@@ -266,16 +267,27 @@
 }
 
 bool CCPPass::ReplaceValues() {
-  bool retval = false;
+  // Even if we make no changes to the function's IR, propagation may have
+  // created new constants.  Even if those constants cannot be replaced in
+  // the IR, the constant definition itself is a change.  To reflect this,
+  // we check whether the next ID to be given by the module is different than
+  // the original bound ID. If that happens, new instructions were added to the
+  // module during propagation.
+  //
+  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/3636 and
+  // https://github.com/KhronosGroup/SPIRV-Tools/issues/3991 for details.
+  bool changed_ir = (context()->module()->IdBound() > original_id_bound_);
+
   for (const auto& it : values_) {
     uint32_t id = it.first;
     uint32_t cst_id = it.second;
     if (!IsVaryingValue(cst_id) && id != cst_id) {
       context()->KillNamesAndDecorates(id);
-      retval |= context()->ReplaceAllUsesWith(id, cst_id);
+      changed_ir |= context()->ReplaceAllUsesWith(id, cst_id);
     }
   }
-  return retval;
+
+  return changed_ir;
 }
 
 bool CCPPass::PropagateConstants(Function* fp) {
@@ -313,6 +325,8 @@
       values_[inst.result_id()] = kVaryingSSAId;
     }
   }
+
+  original_id_bound_ = context()->module()->IdBound();
 }
 
 Pass::Status CCPPass::Process() {
diff --git a/source/opt/ccp_pass.h b/source/opt/ccp_pass.h
index 527f459..fb20c78 100644
--- a/source/opt/ccp_pass.h
+++ b/source/opt/ccp_pass.h
@@ -105,6 +105,10 @@
 
   // Propagator engine used.
   std::unique_ptr<SSAPropagator> propagator_;
+
+  // Value for the module's ID bound before running CCP. Used to detect whether
+  // propagation created new instructions.
+  uint32_t original_id_bound_;
 };
 
 }  // namespace opt
diff --git a/source/opt/compact_ids_pass.cpp b/source/opt/compact_ids_pass.cpp
index 68b940f..6709153 100644
--- a/source/opt/compact_ids_pass.cpp
+++ b/source/opt/compact_ids_pass.cpp
@@ -21,6 +21,24 @@
 
 namespace spvtools {
 namespace opt {
+namespace {
+
+// Returns the remapped id of |id| from |result_id_mapping|. If the remapped
+// id does not exist, adds a new one to |result_id_mapping| and returns it.
+uint32_t GetRemappedId(
+    std::unordered_map<uint32_t, uint32_t>* result_id_mapping, uint32_t id) {
+  auto it = result_id_mapping->find(id);
+  if (it == result_id_mapping->end()) {
+    const uint32_t new_id =
+        static_cast<uint32_t>(result_id_mapping->size()) + 1;
+    const auto insertion_result = result_id_mapping->emplace(id, new_id);
+    it = insertion_result.first;
+    assert(insertion_result.second);
+  }
+  return it->second;
+}
+
+}  // namespace
 
 Pass::Status CompactIdsPass::Process() {
   bool modified = false;
@@ -34,18 +52,10 @@
           if (spvIsIdType(type)) {
             assert(operand->words.size() == 1);
             uint32_t& id = operand->words[0];
-            auto it = result_id_mapping.find(id);
-            if (it == result_id_mapping.end()) {
-              const uint32_t new_id =
-                  static_cast<uint32_t>(result_id_mapping.size()) + 1;
-              const auto insertion_result =
-                  result_id_mapping.emplace(id, new_id);
-              it = insertion_result.first;
-              assert(insertion_result.second);
-            }
-            if (id != it->second) {
+            uint32_t new_id = GetRemappedId(&result_id_mapping, id);
+            if (id != new_id) {
               modified = true;
-              id = it->second;
+              id = new_id;
               // Update data cached in the instruction object.
               if (type == SPV_OPERAND_TYPE_RESULT_ID) {
                 inst->SetResultId(id);
@@ -56,6 +66,23 @@
           }
           ++operand;
         }
+
+        uint32_t scope_id = inst->GetDebugScope().GetLexicalScope();
+        if (scope_id != kNoDebugScope) {
+          uint32_t new_id = GetRemappedId(&result_id_mapping, scope_id);
+          if (scope_id != new_id) {
+            inst->UpdateLexicalScope(new_id);
+            modified = true;
+          }
+        }
+        uint32_t inlinedat_id = inst->GetDebugInlinedAt();
+        if (inlinedat_id != kNoInlinedAt) {
+          uint32_t new_id = GetRemappedId(&result_id_mapping, inlinedat_id);
+          if (inlinedat_id != new_id) {
+            inst->UpdateDebugInlinedAt(new_id);
+            modified = true;
+          }
+        }
       },
       true);
 
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index 6057356..cf24295 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -396,6 +396,12 @@
   return GetDefiningInstruction(c)->result_id();
 }
 
+uint32_t ConstantManager::GetSIntConst(int32_t val) {
+  Type* sint_type = context()->get_type_mgr()->GetSIntType();
+  const Constant* c = GetConstant(sint_type, {static_cast<uint32_t>(val)});
+  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 9518b5b..e17ae6b 100644
--- a/source/opt/constants.h
+++ b/source/opt/constants.h
@@ -630,6 +630,9 @@
   // Returns the id of a 32-bit floating point constant with value |val|.
   uint32_t GetFloatConst(float val);
 
+  // Returns the id of a 32-bit signed integer constant with value |val|.
+  uint32_t GetSIntConst(int32_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/dead_insert_elim_pass.cpp b/source/opt/dead_insert_elim_pass.cpp
index 7d56343..46f4f12 100644
--- a/source/opt/dead_insert_elim_pass.cpp
+++ b/source/opt/dead_insert_elim_pass.cpp
@@ -196,6 +196,7 @@
       }
       const uint32_t id = ii->result_id();
       get_def_use_mgr()->ForEachUser(id, [&ii, this](Instruction* user) {
+        if (user->IsOpenCL100DebugInstr()) return;
         switch (user->opcode()) {
           case SpvOpCompositeInsert:
           case SpvOpPhi:
diff --git a/source/opt/debug_info_manager.cpp b/source/opt/debug_info_manager.cpp
index 6c33764..48285bd 100644
--- a/source/opt/debug_info_manager.cpp
+++ b/source/opt/debug_info_manager.cpp
@@ -33,6 +33,7 @@
 static const uint32_t kDebugDeclareOperandVariableIndex = 5;
 static const uint32_t kDebugValueOperandLocalVariableIndex = 4;
 static const uint32_t kDebugValueOperandExpressionIndex = 6;
+static const uint32_t kDebugValueOperandIndexesIndex = 7;
 static const uint32_t kDebugOperationOperandOperationIndex = 4;
 static const uint32_t kOpVariableOperandStorageClassIndex = 2;
 static const uint32_t kDebugLocalVariableOperandParentIndex = 9;
@@ -98,6 +99,13 @@
   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;
+  }
   assert(
       fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() &&
       "Register DebugFunction for a function that already has DebugFunction");
@@ -371,7 +379,7 @@
       std::move(new_inlined_at));
 }
 
-bool DebugInfoManager::IsDebugDeclared(uint32_t variable_id) {
+bool DebugInfoManager::IsVariableDebugDeclared(uint32_t variable_id) {
   auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
   return dbg_decl_itr != var_id_to_dbg_decl_.end();
 }
@@ -433,8 +441,22 @@
 }
 
 bool DebugInfoManager::IsDeclareVisibleToInstr(Instruction* dbg_declare,
-                                               uint32_t instr_scope_id) {
-  if (instr_scope_id == kNoDebugScope) return false;
+                                               Instruction* scope) {
+  assert(dbg_declare != nullptr);
+  assert(scope != nullptr);
+
+  std::vector<uint32_t> scope_ids;
+  if (scope->opcode() == SpvOpPhi) {
+    scope_ids.push_back(scope->GetDebugScope().GetLexicalScope());
+    for (uint32_t i = 0; i < scope->NumInOperands(); i += 2) {
+      auto* value = context()->get_def_use_mgr()->GetDef(
+          scope->GetSingleWordInOperand(i));
+      if (value != nullptr)
+        scope_ids.push_back(value->GetDebugScope().GetLexicalScope());
+    }
+  } else {
+    scope_ids.push_back(scope->GetDebugScope().GetLexicalScope());
+  }
 
   uint32_t dbg_local_var_id =
       dbg_declare->GetSingleWordOperand(kDebugDeclareOperandLocalVariableIndex);
@@ -445,47 +467,65 @@
 
   // If the scope of DebugDeclare is an ancestor scope of the instruction's
   // scope, the local variable is visible to the instruction.
-  return IsAncestorOfScope(instr_scope_id, decl_scope_id);
+  for (uint32_t scope_id : scope_ids) {
+    if (scope_id != kNoDebugScope &&
+        IsAncestorOfScope(scope_id, decl_scope_id)) {
+      return true;
+    }
+  }
+  return false;
 }
 
-void DebugInfoManager::AddDebugValue(Instruction* scope_and_line,
-                                     uint32_t variable_id, uint32_t value_id,
-                                     Instruction* insert_pos) {
+Instruction* DebugInfoManager::AddDebugValueWithIndex(
+    uint32_t dbg_local_var_id, uint32_t value_id, uint32_t expr_id,
+    uint32_t index_id, Instruction* insert_before) {
+  uint32_t result_id = context()->TakeNextId();
+  if (!result_id) return nullptr;
+  std::unique_ptr<Instruction> new_dbg_value(new Instruction(
+      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_EXTENSION_INSTRUCTION_NUMBER,
+           {static_cast<uint32_t>(OpenCLDebugInfo100DebugValue)}},
+          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {dbg_local_var_id}},
+          {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {value_id}},
+          {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+           {expr_id == 0 ? GetEmptyDebugExpression()->result_id() : expr_id}},
+      }));
+  if (index_id) {
+    new_dbg_value->AddOperand(
+        {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {index_id}});
+  }
+
+  Instruction* added_dbg_value =
+      insert_before->InsertBefore(std::move(new_dbg_value));
+  AnalyzeDebugInst(added_dbg_value);
+  if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
+    context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_value);
+  if (context()->AreAnalysesValid(
+          IRContext::Analysis::kAnalysisInstrToBlockMapping)) {
+    auto insert_blk = context()->get_instr_block(insert_before);
+    context()->set_instr_block(added_dbg_value, insert_blk);
+  }
+  return added_dbg_value;
+}
+
+void DebugInfoManager::AddDebugValueIfVarDeclIsVisible(
+    Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id,
+    Instruction* insert_pos,
+    std::unordered_set<Instruction*>* invisible_decls) {
   auto dbg_decl_itr = var_id_to_dbg_decl_.find(variable_id);
   if (dbg_decl_itr == var_id_to_dbg_decl_.end()) return;
 
-  uint32_t instr_scope_id = scope_and_line->GetDebugScope().GetLexicalScope();
   for (auto* dbg_decl_or_val : dbg_decl_itr->second) {
-    if (!IsDeclareVisibleToInstr(dbg_decl_or_val, instr_scope_id)) continue;
-
-    uint32_t result_id = context()->TakeNextId();
-    std::unique_ptr<Instruction> new_dbg_value(new Instruction(
-        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_EXTENSION_INSTRUCTION_NUMBER,
-             {static_cast<uint32_t>(OpenCLDebugInfo100DebugValue)}},
-            {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-             {dbg_decl_or_val->GetSingleWordOperand(
-                 kDebugValueOperandLocalVariableIndex)}},
-            {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {value_id}},
-            {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-             {GetEmptyDebugExpression()->result_id()}},
-        }));
-
-    if (dbg_decl_or_val->NumOperands() >
-        kDebugValueOperandExpressionIndex + 1) {
-      assert(dbg_decl_or_val->GetOpenCL100DebugOpcode() ==
-             OpenCLDebugInfo100DebugValue);
-      for (uint32_t i = kDebugValueOperandExpressionIndex + 1;
-           i < dbg_decl_or_val->NumOperands(); ++i) {
-        new_dbg_value->AddOperand({spv_operand_type_t::SPV_OPERAND_TYPE_ID,
-                                   {dbg_decl_or_val->GetSingleWordOperand(i)}});
-      }
+    if (scope_and_line &&
+        !IsDeclareVisibleToInstr(dbg_decl_or_val, scope_and_line)) {
+      if (invisible_decls) invisible_decls->insert(dbg_decl_or_val);
+      continue;
     }
 
     // Avoid inserting the new DebugValue between OpPhi or OpVariable
@@ -496,20 +536,47 @@
       insert_before = insert_before->NextNode();
     }
 
-    Instruction* added_dbg_value =
-        insert_before->InsertBefore(std::move(new_dbg_value));
-    added_dbg_value->UpdateDebugInfo(scope_and_line);
-    AnalyzeDebugInst(added_dbg_value);
-    if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
-      context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_value);
-    if (context()->AreAnalysesValid(
-            IRContext::Analysis::kAnalysisInstrToBlockMapping)) {
-      auto insert_blk = context()->get_instr_block(insert_before);
-      context()->set_instr_block(added_dbg_value, insert_blk);
+    uint32_t index_id = 0;
+    if (dbg_decl_or_val->NumOperands() > kDebugValueOperandIndexesIndex) {
+      index_id =
+          dbg_decl_or_val->GetSingleWordOperand(kDebugValueOperandIndexesIndex);
     }
+
+    Instruction* added_dbg_value =
+        AddDebugValueWithIndex(dbg_decl_or_val->GetSingleWordOperand(
+                                   kDebugValueOperandLocalVariableIndex),
+                               value_id, 0, index_id, insert_before);
+    assert(added_dbg_value != nullptr);
+    added_dbg_value->UpdateDebugInfoFrom(scope_and_line ? scope_and_line
+                                                        : dbg_decl_or_val);
+    AnalyzeDebugInst(added_dbg_value);
   }
 }
 
+bool DebugInfoManager::AddDebugValueForDecl(Instruction* dbg_decl,
+                                            uint32_t value_id) {
+  if (dbg_decl == nullptr || !IsDebugDeclare(dbg_decl)) return false;
+
+  std::unique_ptr<Instruction> dbg_val(dbg_decl->Clone(context()));
+  dbg_val->SetResultId(context()->TakeNextId());
+  dbg_val->SetInOperand(kExtInstInstructionInIdx,
+                        {OpenCLDebugInfo100DebugValue});
+  dbg_val->SetOperand(kDebugDeclareOperandVariableIndex, {value_id});
+  dbg_val->SetOperand(kDebugValueOperandExpressionIndex,
+                      {GetEmptyDebugExpression()->result_id()});
+
+  auto* added_dbg_val = dbg_decl->InsertBefore(std::move(dbg_val));
+  AnalyzeDebugInst(added_dbg_val);
+  if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse))
+    context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_val);
+  if (context()->AreAnalysesValid(
+          IRContext::Analysis::kAnalysisInstrToBlockMapping)) {
+    auto insert_blk = context()->get_instr_block(dbg_decl);
+    context()->set_instr_block(added_dbg_val, insert_blk);
+  }
+  return true;
+}
+
 uint32_t DebugInfoManager::GetVariableIdOfDebugValueUsedForDeclare(
     Instruction* inst) {
   if (inst->GetOpenCL100DebugOpcode() != OpenCLDebugInfo100DebugValue) return 0;
@@ -544,42 +611,90 @@
   return 0;
 }
 
-void DebugInfoManager::AnalyzeDebugInst(Instruction* dbg_inst) {
-  if (!dbg_inst->IsOpenCL100DebugInstr()) return;
+bool DebugInfoManager::IsDebugDeclare(Instruction* instr) {
+  if (!instr->IsOpenCL100DebugInstr()) return false;
+  return instr->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare ||
+         GetVariableIdOfDebugValueUsedForDeclare(instr) != 0;
+}
 
-  RegisterDbgInst(dbg_inst);
+void DebugInfoManager::ReplaceAllUsesInDebugScopeWithPredicate(
+    uint32_t before, uint32_t after,
+    const std::function<bool(Instruction*)>& predicate) {
+  auto scope_id_to_users_itr = scope_id_to_users_.find(before);
+  if (scope_id_to_users_itr != scope_id_to_users_.end()) {
+    for (Instruction* inst : scope_id_to_users_itr->second) {
+      if (predicate(inst)) inst->UpdateLexicalScope(after);
+    }
+    scope_id_to_users_[after] = scope_id_to_users_itr->second;
+    scope_id_to_users_.erase(scope_id_to_users_itr);
+  }
+  auto inlinedat_id_to_users_itr = inlinedat_id_to_users_.find(before);
+  if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) {
+    for (Instruction* inst : inlinedat_id_to_users_itr->second) {
+      if (predicate(inst)) inst->UpdateDebugInlinedAt(after);
+    }
+    inlinedat_id_to_users_[after] = inlinedat_id_to_users_itr->second;
+    inlinedat_id_to_users_.erase(inlinedat_id_to_users_itr);
+  }
+}
 
-  if (dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) {
-    assert(GetDebugFunction(dbg_inst->GetSingleWordOperand(
+void DebugInfoManager::ClearDebugScopeAndInlinedAtUses(Instruction* inst) {
+  auto scope_id_to_users_itr = scope_id_to_users_.find(inst->result_id());
+  if (scope_id_to_users_itr != scope_id_to_users_.end()) {
+    scope_id_to_users_.erase(scope_id_to_users_itr);
+  }
+  auto inlinedat_id_to_users_itr =
+      inlinedat_id_to_users_.find(inst->result_id());
+  if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) {
+    inlinedat_id_to_users_.erase(inlinedat_id_to_users_itr);
+  }
+}
+
+void DebugInfoManager::AnalyzeDebugInst(Instruction* inst) {
+  if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) {
+    auto& users = scope_id_to_users_[inst->GetDebugScope().GetLexicalScope()];
+    users.insert(inst);
+  }
+  if (inst->GetDebugInlinedAt() != kNoInlinedAt) {
+    auto& users = inlinedat_id_to_users_[inst->GetDebugInlinedAt()];
+    users.insert(inst);
+  }
+
+  if (!inst->IsOpenCL100DebugInstr()) return;
+
+  RegisterDbgInst(inst);
+
+  if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction) {
+    assert(GetDebugFunction(inst->GetSingleWordOperand(
                kDebugFunctionOperandFunctionIndex)) == nullptr &&
            "Two DebugFunction instruction exists for a single OpFunction.");
-    RegisterDbgFunction(dbg_inst);
+    RegisterDbgFunction(inst);
   }
 
   if (deref_operation_ == nullptr &&
-      dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugOperation &&
-      dbg_inst->GetSingleWordOperand(kDebugOperationOperandOperationIndex) ==
+      inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugOperation &&
+      inst->GetSingleWordOperand(kDebugOperationOperandOperationIndex) ==
           OpenCLDebugInfo100Deref) {
-    deref_operation_ = dbg_inst;
+    deref_operation_ = inst;
   }
 
   if (debug_info_none_inst_ == nullptr &&
-      dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugInfoNone) {
-    debug_info_none_inst_ = dbg_inst;
+      inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugInfoNone) {
+    debug_info_none_inst_ = inst;
   }
 
-  if (empty_debug_expr_inst_ == nullptr && IsEmptyDebugExpression(dbg_inst)) {
-    empty_debug_expr_inst_ = dbg_inst;
+  if (empty_debug_expr_inst_ == nullptr && IsEmptyDebugExpression(inst)) {
+    empty_debug_expr_inst_ = inst;
   }
 
-  if (dbg_inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) {
+  if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) {
     uint32_t var_id =
-        dbg_inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
-    RegisterDbgDeclare(var_id, dbg_inst);
+        inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex);
+    RegisterDbgDeclare(var_id, inst);
   }
 
-  if (uint32_t var_id = GetVariableIdOfDebugValueUsedForDeclare(dbg_inst)) {
-    RegisterDbgDeclare(var_id, dbg_inst);
+  if (uint32_t var_id = GetVariableIdOfDebugValueUsedForDeclare(inst)) {
+    RegisterDbgDeclare(var_id, inst);
   }
 }
 
@@ -659,6 +774,17 @@
 }
 
 void DebugInfoManager::ClearDebugInfo(Instruction* instr) {
+  auto scope_id_to_users_itr =
+      scope_id_to_users_.find(instr->GetDebugScope().GetLexicalScope());
+  if (scope_id_to_users_itr != scope_id_to_users_.end()) {
+    scope_id_to_users_itr->second.erase(instr);
+  }
+  auto inlinedat_id_to_users_itr =
+      inlinedat_id_to_users_.find(instr->GetDebugInlinedAt());
+  if (inlinedat_id_to_users_itr != inlinedat_id_to_users_.end()) {
+    inlinedat_id_to_users_itr->second.erase(instr);
+  }
+
   if (instr == nullptr || !instr->IsOpenCL100DebugInstr()) {
     return;
   }
diff --git a/source/opt/debug_info_manager.h b/source/opt/debug_info_manager.h
index 6592109..edb11b7 100644
--- a/source/opt/debug_info_manager.h
+++ b/source/opt/debug_info_manager.h
@@ -133,17 +133,34 @@
   uint32_t BuildDebugInlinedAtChain(uint32_t callee_inlined_at,
                                     DebugInlinedAtContext* inlined_at_ctx);
 
-  // Return true if |variable_id| has DebugDeclare or DebugVal.
-  bool IsDebugDeclared(uint32_t variable_id);
+  // Returns true if there is a debug declaration instruction whose
+  // 'Local Variable' operand is |variable_id|.
+  bool IsVariableDebugDeclared(uint32_t variable_id);
 
-  // Kill all DebugDeclares for |variable_id|
+  // Kills all debug declaration instructions with Deref whose 'Local Variable'
+  // operand is |variable_id|.
   void KillDebugDeclares(uint32_t variable_id);
 
   // Generates a DebugValue instruction with value |value_id| for every local
   // variable that is in the scope of |scope_and_line| and whose memory is
   // |variable_id| and inserts it after the instruction |insert_pos|.
-  void AddDebugValue(Instruction* scope_and_line, uint32_t variable_id,
-                     uint32_t value_id, Instruction* insert_pos);
+  // |invisible_decls| returns DebugDeclares invisible to |scope_and_line|.
+  void AddDebugValueIfVarDeclIsVisible(
+      Instruction* scope_and_line, uint32_t variable_id, uint32_t value_id,
+      Instruction* insert_pos,
+      std::unordered_set<Instruction*>* invisible_decls);
+
+  // Generates a DebugValue instruction with |dbg_local_var_id|, |value_id|,
+  // |expr_id|, |index_id| operands and inserts it before |insert_before|.
+  Instruction* AddDebugValueWithIndex(uint32_t dbg_local_var_id,
+                                      uint32_t value_id, uint32_t expr_id,
+                                      uint32_t index_id,
+                                      Instruction* insert_before);
+
+  // Adds DebugValue for DebugDeclare |dbg_decl|. The new DebugValue has the
+  // same line, scope, and operands but it uses |value_id| for value. Returns
+  // weather it succeeds or not.
+  bool AddDebugValueForDecl(Instruction* dbg_decl, uint32_t value_id);
 
   // Erases |instr| from data structures of this class.
   void ClearDebugInfo(Instruction* instr);
@@ -158,6 +175,19 @@
   void ConvertDebugGlobalToLocalVariable(Instruction* dbg_global_var,
                                          Instruction* local_var);
 
+  // Returns true if |instr| is a debug declaration instruction.
+  bool IsDebugDeclare(Instruction* instr);
+
+  // Replace all uses of |before| id that is an operand of a DebugScope with
+  // |after| id if those uses (instruction) return true for |predicate|.
+  void ReplaceAllUsesInDebugScopeWithPredicate(
+      uint32_t before, uint32_t after,
+      const std::function<bool(Instruction*)>& predicate);
+
+  // Removes uses of DebugScope |inst| from |scope_id_to_users_| or uses of
+  // DebugInlinedAt |inst| from |inlinedat_id_to_users_|.
+  void ClearDebugScopeAndInlinedAtUses(Instruction* inst);
+
  private:
   IRContext* context() { return context_; }
 
@@ -194,8 +224,7 @@
 
   // Returns true if the declaration of a local variable |dbg_declare|
   // is visible in the scope of an instruction |instr_scope_id|.
-  bool IsDeclareVisibleToInstr(Instruction* dbg_declare,
-                               uint32_t instr_scope_id);
+  bool IsDeclareVisibleToInstr(Instruction* dbg_declare, Instruction* scope);
 
   // Returns the parent scope of the scope |child_scope|.
   uint32_t GetParentScope(uint32_t child_scope);
@@ -215,6 +244,14 @@
   std::unordered_map<uint32_t, std::unordered_set<Instruction*>>
       var_id_to_dbg_decl_;
 
+  // Mapping from DebugScope ids to users.
+  std::unordered_map<uint32_t, std::unordered_set<Instruction*>>
+      scope_id_to_users_;
+
+  // Mapping from DebugInlinedAt ids to users.
+  std::unordered_map<uint32_t, std::unordered_set<Instruction*>>
+      inlinedat_id_to_users_;
+
   // DebugOperation whose OpCode is OpenCLDebugInfo100Deref.
   Instruction* deref_operation_;
 
diff --git a/source/opt/dominator_tree.cpp b/source/opt/dominator_tree.cpp
index 7e61506..55287f4 100644
--- a/source/opt/dominator_tree.cpp
+++ b/source/opt/dominator_tree.cpp
@@ -22,8 +22,8 @@
 
 // Calculates the dominator or postdominator tree for a given function.
 // 1 - Compute the successors and predecessors for each BasicBlock. We add a
-// dummy node for the start node or for postdominators the exit. This node will
-// point to all entry or all exit nodes.
+// placeholder node for the start node or for postdominators the exit. This node
+// will point to all entry or all exit nodes.
 // 2 - Using the CFA::DepthFirstTraversal get a depth first postordered list of
 // all BasicBlocks. Using the successors (or for postdominator, predecessors)
 // calculated in step 1 to traverse the tree.
@@ -107,8 +107,9 @@
 
  public:
   // For compliance with the dominance tree computation, entry nodes are
-  // connected to a single dummy node.
-  BasicBlockSuccessorHelper(Function& func, const BasicBlock* dummy_start_node,
+  // connected to a single placeholder node.
+  BasicBlockSuccessorHelper(Function& func,
+                            const BasicBlock* placeholder_start_node,
                             bool post);
 
   // CFA::CalculateDominators requires std::vector<BasicBlock*>.
@@ -139,23 +140,24 @@
   // Build the successors and predecessors map for each basic blocks |f|.
   // If |invert_graph_| is true, all edges are reversed (successors becomes
   // predecessors and vice versa).
-  // For convenience, the start of the graph is |dummy_start_node|.
+  // For convenience, the start of the graph is |placeholder_start_node|.
   // The dominator tree construction requires a unique entry node, which cannot
-  // be guaranteed for the postdominator graph. The |dummy_start_node| BB is
-  // here to gather all entry nodes.
-  void CreateSuccessorMap(Function& f, const BasicBlock* dummy_start_node);
+  // be guaranteed for the postdominator graph. The |placeholder_start_node| BB
+  // is here to gather all entry nodes.
+  void CreateSuccessorMap(Function& f,
+                          const BasicBlock* placeholder_start_node);
 };
 
 template <typename BBType>
 BasicBlockSuccessorHelper<BBType>::BasicBlockSuccessorHelper(
-    Function& func, const BasicBlock* dummy_start_node, bool invert)
+    Function& func, const BasicBlock* placeholder_start_node, bool invert)
     : invert_graph_(invert) {
-  CreateSuccessorMap(func, dummy_start_node);
+  CreateSuccessorMap(func, placeholder_start_node);
 }
 
 template <typename BBType>
 void BasicBlockSuccessorHelper<BBType>::CreateSuccessorMap(
-    Function& f, const BasicBlock* dummy_start_node) {
+    Function& f, const BasicBlock* placeholder_start_node) {
   std::map<uint32_t, BasicBlock*> id_to_BB_map;
   auto GetSuccessorBasicBlock = [&f, &id_to_BB_map](uint32_t successor_id) {
     BasicBlock*& Succ = id_to_BB_map[successor_id];
@@ -173,11 +175,10 @@
   if (invert_graph_) {
     // For the post dominator tree, we see the inverted graph.
     // successors_ in the inverted graph are the predecessors in the CFG.
-    // The tree construction requires 1 entry point, so we add a dummy node
-    // that is connected to all function exiting basic blocks.
-    // An exiting basic block is a block with an OpKill, OpUnreachable,
-    // OpReturn, OpReturnValue, or OpTerminateInvocation  as terminator
-    // instruction.
+    // The tree construction requires 1 entry point, so we add a placeholder
+    // node that is connected to all function exiting basic blocks. An exiting
+    // basic block is a block with an OpKill, OpUnreachable, OpReturn,
+    // OpReturnValue, or OpTerminateInvocation  as terminator instruction.
     for (BasicBlock& bb : f) {
       if (bb.hasSuccessor()) {
         BasicBlockListTy& pred_list = predecessors_[&bb];
@@ -192,14 +193,15 @@
               pred_list.push_back(succ);
             });
       } else {
-        successors_[dummy_start_node].push_back(&bb);
-        predecessors_[&bb].push_back(const_cast<BasicBlock*>(dummy_start_node));
+        successors_[placeholder_start_node].push_back(&bb);
+        predecessors_[&bb].push_back(
+            const_cast<BasicBlock*>(placeholder_start_node));
       }
     }
   } else {
-    successors_[dummy_start_node].push_back(f.entry().get());
+    successors_[placeholder_start_node].push_back(f.entry().get());
     predecessors_[f.entry().get()].push_back(
-        const_cast<BasicBlock*>(dummy_start_node));
+        const_cast<BasicBlock*>(placeholder_start_node));
     for (BasicBlock& bb : f) {
       BasicBlockListTy& succ_list = successors_[&bb];
 
@@ -288,7 +290,7 @@
 }
 
 void DominatorTree::GetDominatorEdges(
-    const Function* f, const BasicBlock* dummy_start_node,
+    const Function* f, const BasicBlock* placeholder_start_node,
     std::vector<std::pair<BasicBlock*, BasicBlock*>>* edges) {
   // Each time the depth first traversal calls the postorder callback
   // std::function we push that node into the postorder vector to create our
@@ -302,7 +304,7 @@
   // BB are derived from F, so we need to const cast it at some point
   // no modification is made on F.
   BasicBlockSuccessorHelper<BasicBlock> helper{
-      *const_cast<Function*>(f), dummy_start_node, postdominator_};
+      *const_cast<Function*>(f), placeholder_start_node, postdominator_};
 
   // The successor function tells DepthFirstTraversal how to move to successive
   // nodes by providing an interface to get a list of successor nodes from any
@@ -316,7 +318,7 @@
   // If we're building a post dominator tree we traverse the tree in reverse
   // using the predecessor function in place of the successor function and vice
   // versa.
-  DepthFirstSearchPostOrder(dummy_start_node, successor_functor,
+  DepthFirstSearchPostOrder(placeholder_start_node, successor_functor,
                             postorder_function);
   *edges = CFA<BasicBlock>::CalculateDominators(postorder, predecessor_functor);
 }
@@ -329,12 +331,12 @@
     return;
   }
 
-  const BasicBlock* dummy_start_node =
+  const BasicBlock* placeholder_start_node =
       postdominator_ ? cfg.pseudo_exit_block() : cfg.pseudo_entry_block();
 
   // Get the immediate dominator for each node.
   std::vector<std::pair<BasicBlock*, BasicBlock*>> edges;
-  GetDominatorEdges(f, dummy_start_node, &edges);
+  GetDominatorEdges(f, placeholder_start_node, &edges);
 
   // Transform the vector<pair> into the tree structure which we can use to
   // efficiently query dominance.
@@ -380,7 +382,7 @@
     }
 
     // Print the arrow from the parent to this node. Entry nodes will not have
-    // parents so draw them as children from the dummy node.
+    // parents so draw them as children from the placeholder node.
     if (node->parent_) {
       out_stream << node->parent_->bb_->id() << " -> " << node->bb_->id()
                  << ";\n";
diff --git a/source/opt/eliminate_dead_functions_util.cpp b/source/opt/eliminate_dead_functions_util.cpp
index 8a38959..6b5234b 100644
--- a/source/opt/eliminate_dead_functions_util.cpp
+++ b/source/opt/eliminate_dead_functions_util.cpp
@@ -21,9 +21,35 @@
 
 Module::iterator EliminateFunction(IRContext* context,
                                    Module::iterator* func_iter) {
+  bool first_func = *func_iter == context->module()->begin();
+  bool seen_func_end = false;
   (*func_iter)
-      ->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
-                    true);
+      ->ForEachInst(
+          [context, first_func, func_iter, &seen_func_end](Instruction* inst) {
+            if (inst->opcode() == SpvOpFunctionEnd) {
+              seen_func_end = true;
+            }
+            // Move non-semantic instructions to the previous function or
+            // global values if this is the first function.
+            if (seen_func_end && inst->opcode() == SpvOpExtInst) {
+              assert(inst->IsNonSemanticInstruction());
+              std::unique_ptr<Instruction> clone(inst->Clone(context));
+              context->ForgetUses(inst);
+              context->AnalyzeDefUse(clone.get());
+              if (first_func) {
+                context->AddGlobalValue(std::move(clone));
+              } else {
+                auto prev_func_iter = *func_iter;
+                --prev_func_iter;
+                prev_func_iter->AddNonSemanticInstruction(std::move(clone));
+              }
+              inst->ToNop();
+            } else {
+              context->KillNonSemanticInfo(inst);
+              context->KillInst(inst);
+            }
+          },
+          true, true);
   return func_iter->Erase();
 }
 
diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp
index 1c8cdc8..010eec9 100644
--- a/source/opt/folding_rules.cpp
+++ b/source/opt/folding_rules.cpp
@@ -1467,7 +1467,7 @@
             type_mgr->GetType(element_def->type_id())->AsVector();
         if (element_type) {
           uint32_t vector_size = element_type->element_count();
-          if (vector_size < element_index) {
+          if (vector_size <= element_index) {
             // The element we want comes after this vector.
             element_index -= vector_size;
           } else {
diff --git a/source/opt/function.cpp b/source/opt/function.cpp
index 320f8ca..52054ea 100644
--- a/source/opt/function.cpp
+++ b/source/opt/function.cpp
@@ -47,31 +47,40 @@
   }
 
   clone->SetFunctionEnd(std::unique_ptr<Instruction>(EndInst()->Clone(ctx)));
+
+  clone->non_semantic_.reserve(non_semantic_.size());
+  for (auto& non_semantic : non_semantic_) {
+    clone->AddNonSemanticInstruction(
+        std::unique_ptr<Instruction>(non_semantic->Clone(ctx)));
+  }
   return clone;
 }
 
 void Function::ForEachInst(const std::function<void(Instruction*)>& f,
-                           bool run_on_debug_line_insts) {
+                           bool run_on_debug_line_insts,
+                           bool run_on_non_semantic_insts) {
   WhileEachInst(
       [&f](Instruction* inst) {
         f(inst);
         return true;
       },
-      run_on_debug_line_insts);
+      run_on_debug_line_insts, run_on_non_semantic_insts);
 }
 
 void Function::ForEachInst(const std::function<void(const Instruction*)>& f,
-                           bool run_on_debug_line_insts) const {
+                           bool run_on_debug_line_insts,
+                           bool run_on_non_semantic_insts) const {
   WhileEachInst(
       [&f](const Instruction* inst) {
         f(inst);
         return true;
       },
-      run_on_debug_line_insts);
+      run_on_debug_line_insts, run_on_non_semantic_insts);
 }
 
 bool Function::WhileEachInst(const std::function<bool(Instruction*)>& f,
-                             bool run_on_debug_line_insts) {
+                             bool run_on_debug_line_insts,
+                             bool run_on_non_semantic_insts) {
   if (def_inst_) {
     if (!def_inst_->WhileEachInst(f, run_on_debug_line_insts)) {
       return false;
@@ -99,13 +108,26 @@
     }
   }
 
-  if (end_inst_) return end_inst_->WhileEachInst(f, run_on_debug_line_insts);
+  if (end_inst_) {
+    if (!end_inst_->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  if (run_on_non_semantic_insts) {
+    for (auto& non_semantic : non_semantic_) {
+      if (!non_semantic->WhileEachInst(f, run_on_debug_line_insts)) {
+        return false;
+      }
+    }
+  }
 
   return true;
 }
 
 bool Function::WhileEachInst(const std::function<bool(const Instruction*)>& f,
-                             bool run_on_debug_line_insts) const {
+                             bool run_on_debug_line_insts,
+                             bool run_on_non_semantic_insts) const {
   if (def_inst_) {
     if (!static_cast<const Instruction*>(def_inst_.get())
              ->WhileEachInst(f, run_on_debug_line_insts)) {
@@ -133,9 +155,21 @@
     }
   }
 
-  if (end_inst_)
-    return static_cast<const Instruction*>(end_inst_.get())
-        ->WhileEachInst(f, run_on_debug_line_insts);
+  if (end_inst_) {
+    if (!static_cast<const Instruction*>(end_inst_.get())
+             ->WhileEachInst(f, run_on_debug_line_insts)) {
+      return false;
+    }
+  }
+
+  if (run_on_non_semantic_insts) {
+    for (auto& non_semantic : non_semantic_) {
+      if (!static_cast<const Instruction*>(non_semantic.get())
+               ->WhileEachInst(f, run_on_debug_line_insts)) {
+        return false;
+      }
+    }
+  }
 
   return true;
 }
@@ -193,6 +227,18 @@
   return nullptr;
 }
 
+bool Function::HasEarlyReturn() const {
+  auto post_dominator_analysis =
+      blocks_.front()->GetLabel()->context()->GetPostDominatorAnalysis(this);
+  for (auto& block : blocks_) {
+    if (spvOpcodeIsReturn(block->tail()->opcode()) &&
+        !post_dominator_analysis->Dominates(block.get(), entry().get())) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool Function::IsRecursive() const {
   IRContext* ctx = blocks_.front()->GetLabel()->context();
   IRContext::ProcessFunction mark_visited = [this](Function* fp) {
diff --git a/source/opt/function.h b/source/opt/function.h
index f5035f0..4b20dcb 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -79,6 +79,11 @@
   // Saves the given function end instruction.
   inline void SetFunctionEnd(std::unique_ptr<Instruction> end_inst);
 
+  // Add a non-semantic instruction that succeeds this function in the module.
+  // These instructions are maintained in the order they are added.
+  inline void AddNonSemanticInstruction(
+      std::unique_ptr<Instruction> non_semantic);
+
   // Returns the given function end instruction.
   inline Instruction* EndInst() { return end_inst_.get(); }
   inline const Instruction* EndInst() const { return end_inst_.get(); }
@@ -89,6 +94,9 @@
   // Returns function's return type id
   inline uint32_t type_id() const { return def_inst_->type_id(); }
 
+  // Returns the function's control mask
+  inline uint32_t control_mask() const { return def_inst_->GetSingleWordInOperand(0); }
+
   // Returns the entry basic block for this function.
   const std::unique_ptr<BasicBlock>& entry() const { return blocks_.front(); }
 
@@ -115,19 +123,24 @@
   }
 
   // Runs the given function |f| on instructions in this function, in order,
-  // and optionally on debug line instructions that might precede them.
+  // and optionally on debug line instructions that might precede them and
+  // non-semantic instructions that succceed the function.
   void ForEachInst(const std::function<void(Instruction*)>& f,
-                   bool run_on_debug_line_insts = false);
+                   bool run_on_debug_line_insts = false,
+                   bool run_on_non_semantic_insts = false);
   void ForEachInst(const std::function<void(const Instruction*)>& f,
-                   bool run_on_debug_line_insts = false) const;
+                   bool run_on_debug_line_insts = false,
+                   bool run_on_non_semantic_insts = false) const;
   // Runs the given function |f| on instructions in this function, in order,
-  // and optionally on debug line instructions that might precede them.
-  // If |f| returns false, iteration is terminated and this function returns
-  // false.
+  // and optionally on debug line instructions that might precede them and
+  // non-semantic instructions that succeed the function.  If |f| returns
+  // false, iteration is terminated and this function returns false.
   bool WhileEachInst(const std::function<bool(Instruction*)>& f,
-                     bool run_on_debug_line_insts = false);
+                     bool run_on_debug_line_insts = false,
+                     bool run_on_non_semantic_insts = false);
   bool WhileEachInst(const std::function<bool(const Instruction*)>& f,
-                     bool run_on_debug_line_insts = false) const;
+                     bool run_on_debug_line_insts = false,
+                     bool run_on_non_semantic_insts = false) const;
 
   // Runs the given function |f| on each parameter instruction in this function,
   // in order, and optionally on debug line instructions that might precede
@@ -148,7 +161,10 @@
   BasicBlock* InsertBasicBlockBefore(std::unique_ptr<BasicBlock>&& new_block,
                                      BasicBlock* position);
 
-  // Return true if the function calls itself either directly or indirectly.
+  // Returns true if the function has a return block other than the exit block.
+  bool HasEarlyReturn() const;
+
+  // Returns true if the function calls itself either directly or indirectly.
   bool IsRecursive() const;
 
   // Pretty-prints all the basic blocks in this function into a std::string.
@@ -172,6 +188,8 @@
   std::vector<std::unique_ptr<BasicBlock>> blocks_;
   // The OpFunctionEnd instruction.
   std::unique_ptr<Instruction> end_inst_;
+  // Non-semantic instructions succeeded by this function.
+  std::vector<std::unique_ptr<Instruction>> non_semantic_;
 };
 
 // Pretty-prints |func| to |str|. Returns |str|.
@@ -235,6 +253,11 @@
   end_inst_ = std::move(end_inst);
 }
 
+inline void Function::AddNonSemanticInstruction(
+    std::unique_ptr<Instruction> non_semantic) {
+  non_semantic_.emplace_back(std::move(non_semantic));
+}
+
 }  // namespace opt
 }  // namespace spvtools
 
diff --git a/source/opt/graphics_robust_access_pass.cpp b/source/opt/graphics_robust_access_pass.cpp
index db14020..46483e4 100644
--- a/source/opt/graphics_robust_access_pass.cpp
+++ b/source/opt/graphics_robust_access_pass.cpp
@@ -320,9 +320,13 @@
       maxval_width *= 2;
     }
     // Determine the type for |maxval|.
+    uint32_t next_id = context()->module()->IdBound();
     analysis::Integer signed_type_for_query(maxval_width, true);
     auto* maxval_type =
         type_mgr->GetRegisteredType(&signed_type_for_query)->AsInteger();
+    if (next_id != context()->module()->IdBound()) {
+      module_status_.modified = true;
+    }
     // Access chain indices are treated as signed, so limit the maximum value
     // of the index so it will always be positive for a signed clamp operation.
     maxval = std::min(maxval, ((uint64_t(1) << (maxval_width - 1)) - 1));
diff --git a/source/opt/if_conversion.cpp b/source/opt/if_conversion.cpp
index 104182b..4284069 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());
+        select->UpdateDebugInfoFrom(phi);
         context()->ReplaceAllUsesWith(phi->result_id(), select->result_id());
         to_kill.push_back(phi);
         modified = true;
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index ef94d0d..eaf29aa 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -619,6 +619,14 @@
     assert(resId != 0);
     AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr,
             call_inst_itr->dbg_line_inst(), call_inst_itr->GetDebugScope());
+  } else {
+    // Even though it is very unlikely, it is possible that the result id of
+    // the void-function call is used, so we need to generate an instruction
+    // with that result id.
+    std::unique_ptr<Instruction> undef_inst(
+        new Instruction(context(), SpvOpUndef, call_inst_itr->type_id(),
+                        call_inst_itr->result_id(), {}));
+    context()->AddGlobalValue(std::move(undef_inst));
   }
 
   // Move instructions of original caller block after call instruction.
@@ -719,6 +727,12 @@
 bool InlinePass::IsInlinableFunction(Function* func) {
   // We can only inline a function if it has blocks.
   if (func->cbegin() == func->cend()) return false;
+
+  // Do not inline functions with DontInline flag.
+  if (func->control_mask() & SpvFunctionControlDontInlineMask) {
+    return false;
+  }
+
   // Do not inline functions with returns in loops. Currently early return
   // functions are inlined by wrapping them in a one trip loop and implementing
   // the returns as a branch to the loop's merge block. However, this can only
diff --git a/source/opt/inst_bindless_check_pass.cpp b/source/opt/inst_bindless_check_pass.cpp
index 4587343..64d389c 100644
--- a/source/opt/inst_bindless_check_pass.cpp
+++ b/source/opt/inst_bindless_check_pass.cpp
@@ -26,13 +26,19 @@
 static const int kSpvLoadPtrIdInIdx = 0;
 static const int kSpvAccessChainBaseIdInIdx = 0;
 static const int kSpvAccessChainIndex0IdInIdx = 1;
-static const int kSpvTypePointerTypeIdInIdx = 1;
 static const int kSpvTypeArrayLengthIdInIdx = 1;
 static const int kSpvConstantValueInIdx = 0;
 static const int kSpvVariableStorageClassInIdx = 0;
 
 }  // anonymous namespace
 
+// Avoid unused variable warning/error on Linux
+#ifndef NDEBUG
+#define USE_ASSERT(x) assert(x)
+#else
+#define USE_ASSERT(x) ((void)(x))
+#endif
+
 namespace spvtools {
 namespace opt {
 
@@ -48,14 +54,25 @@
 uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
                                                  uint32_t desc_idx_id,
                                                  InstructionBuilder* builder) {
-  uint32_t desc_set_base_id =
-      builder->GetUintConstantId(kDebugInputBindlessInitOffset);
-  uint32_t desc_set_idx_id = builder->GetUintConstantId(var2desc_set_[var_id]);
   uint32_t binding_idx_id = builder->GetUintConstantId(var2binding_[var_id]);
   uint32_t u_desc_idx_id = GenUintCastCode(desc_idx_id, builder);
-  return GenDebugDirectRead(
-      {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
-      builder);
+  // If desc index checking is not enabled, we know the offset of initialization
+  // entries is 1, so we can avoid loading this value and just add 1 to the
+  // descriptor set.
+  if (!desc_idx_enabled_) {
+    uint32_t desc_set_idx_id =
+        builder->GetUintConstantId(var2desc_set_[var_id] + 1);
+    return GenDebugDirectRead({desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+                              builder);
+  } else {
+    uint32_t desc_set_base_id =
+        builder->GetUintConstantId(kDebugInputBindlessInitOffset);
+    uint32_t desc_set_idx_id =
+        builder->GetUintConstantId(var2desc_set_[var_id]);
+    return GenDebugDirectRead(
+        {desc_set_base_id, desc_set_idx_id, binding_idx_id, u_desc_idx_id},
+        builder);
+  }
 }
 
 uint32_t InstBindlessCheckPass::CloneOriginalReference(
@@ -156,13 +173,9 @@
   return 0;
 }
 
-Instruction* InstBindlessCheckPass::GetDescriptorTypeInst(
-    Instruction* var_inst) {
-  uint32_t var_type_id = var_inst->type_id();
-  Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
-  uint32_t desc_type_id =
-      var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
-  return get_def_use_mgr()->GetDef(desc_type_id);
+Instruction* InstBindlessCheckPass::GetPointeeTypeInst(Instruction* ptr_inst) {
+  uint32_t pte_ty_id = GetPointeeTypeId(ptr_inst);
+  return get_def_use_mgr()->GetDef(pte_ty_id);
 }
 
 bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
@@ -187,7 +200,7 @@
         return false;
         break;
     }
-    Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
+    Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
     switch (desc_type_inst->opcode()) {
       case SpvOpTypeArray:
       case SpvOpTypeRuntimeArray:
@@ -195,11 +208,11 @@
         // do not want to instrument loads of descriptors here which are part of
         // an image-based reference.
         if (ptr_inst->NumInOperands() < 3) return false;
-        ref->index_id =
+        ref->desc_idx_id =
             ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
         break;
       default:
-        ref->index_id = 0;
+        ref->desc_idx_id = 0;
         break;
     }
     return true;
@@ -229,14 +242,14 @@
   ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
   Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
   if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
-    ref->index_id = 0;
+    ref->desc_idx_id = 0;
     ref->var_id = ref->ptr_id;
   } else if (ptr_inst->opcode() == SpvOp::SpvOpAccessChain) {
     if (ptr_inst->NumInOperands() != 2) {
       assert(false && "unexpected bindless index number");
       return false;
     }
-    ref->index_id =
+    ref->desc_idx_id =
         ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
     ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
     Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
@@ -251,9 +264,152 @@
   return true;
 }
 
+uint32_t InstBindlessCheckPass::FindStride(uint32_t ty_id,
+                                           uint32_t stride_deco) {
+  uint32_t stride = 0xdeadbeef;
+  bool found = !get_decoration_mgr()->WhileEachDecoration(
+      ty_id, stride_deco, [&stride](const Instruction& deco_inst) {
+        stride = deco_inst.GetSingleWordInOperand(2u);
+        return false;
+      });
+  USE_ASSERT(found && "stride not found");
+  return stride;
+}
+
+uint32_t InstBindlessCheckPass::ByteSize(uint32_t ty_id) {
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  const analysis::Type* sz_ty = type_mgr->GetType(ty_id);
+  if (sz_ty->kind() == analysis::Type::kPointer) {
+    // Assuming PhysicalStorageBuffer pointer
+    return 8;
+  }
+  uint32_t size = 1;
+  if (sz_ty->kind() == analysis::Type::kMatrix) {
+    const analysis::Matrix* m_ty = sz_ty->AsMatrix();
+    size = m_ty->element_count() * size;
+    uint32_t stride = FindStride(ty_id, SpvDecorationMatrixStride);
+    if (stride != 0) return size * stride;
+    sz_ty = m_ty->element_type();
+  }
+  if (sz_ty->kind() == analysis::Type::kVector) {
+    const analysis::Vector* v_ty = sz_ty->AsVector();
+    size = v_ty->element_count() * size;
+    sz_ty = v_ty->element_type();
+  }
+  switch (sz_ty->kind()) {
+    case analysis::Type::kFloat: {
+      const analysis::Float* f_ty = sz_ty->AsFloat();
+      size *= f_ty->width();
+    } break;
+    case analysis::Type::kInteger: {
+      const analysis::Integer* i_ty = sz_ty->AsInteger();
+      size *= i_ty->width();
+    } break;
+    default: { assert(false && "unexpected type"); } break;
+  }
+  size /= 8;
+  return size;
+}
+
+uint32_t InstBindlessCheckPass::GenLastByteIdx(ref_analysis* ref,
+                                               InstructionBuilder* builder) {
+  // Find outermost buffer type and its access chain index
+  Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
+  Instruction* desc_ty_inst = GetPointeeTypeInst(var_inst);
+  uint32_t buff_ty_id;
+  uint32_t ac_in_idx = 1;
+  switch (desc_ty_inst->opcode()) {
+    case SpvOpTypeArray:
+    case SpvOpTypeRuntimeArray:
+      buff_ty_id = desc_ty_inst->GetSingleWordInOperand(0);
+      ++ac_in_idx;
+      break;
+    default:
+      assert(desc_ty_inst->opcode() == SpvOpTypeStruct &&
+             "unexpected descriptor type");
+      buff_ty_id = desc_ty_inst->result_id();
+      break;
+  }
+  // Process remaining access chain indices
+  Instruction* ac_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
+  uint32_t curr_ty_id = buff_ty_id;
+  uint32_t sum_id = 0;
+  while (ac_in_idx < ac_inst->NumInOperands()) {
+    uint32_t curr_idx_id = ac_inst->GetSingleWordInOperand(ac_in_idx);
+    Instruction* curr_idx_inst = get_def_use_mgr()->GetDef(curr_idx_id);
+    Instruction* curr_ty_inst = get_def_use_mgr()->GetDef(curr_ty_id);
+    uint32_t curr_offset_id = 0;
+    switch (curr_ty_inst->opcode()) {
+      case SpvOpTypeArray:
+      case SpvOpTypeRuntimeArray:
+      case SpvOpTypeMatrix: {
+        // Get array/matrix stride and multiply by current index
+        uint32_t stride_deco = (curr_ty_inst->opcode() == SpvOpTypeMatrix)
+                                   ? SpvDecorationMatrixStride
+                                   : SpvDecorationArrayStride;
+        uint32_t arr_stride = FindStride(curr_ty_id, stride_deco);
+        uint32_t arr_stride_id = builder->GetUintConstantId(arr_stride);
+        uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
+        Instruction* curr_offset_inst = builder->AddBinaryOp(
+            GetUintId(), SpvOpIMul, arr_stride_id, curr_idx_32b_id);
+        curr_offset_id = curr_offset_inst->result_id();
+        // Get element type for next step
+        curr_ty_id = curr_ty_inst->GetSingleWordInOperand(0);
+      } break;
+      case SpvOpTypeVector: {
+        // Stride is size of component type
+        uint32_t comp_ty_id = curr_ty_inst->GetSingleWordInOperand(0u);
+        uint32_t vec_stride = ByteSize(comp_ty_id);
+        uint32_t vec_stride_id = builder->GetUintConstantId(vec_stride);
+        uint32_t curr_idx_32b_id = Gen32BitCvtCode(curr_idx_id, builder);
+        Instruction* curr_offset_inst = builder->AddBinaryOp(
+            GetUintId(), SpvOpIMul, vec_stride_id, curr_idx_32b_id);
+        curr_offset_id = curr_offset_inst->result_id();
+        // Get element type for next step
+        curr_ty_id = comp_ty_id;
+      } break;
+      case SpvOpTypeStruct: {
+        // Get buffer byte offset for the referenced member
+        assert(curr_idx_inst->opcode() == SpvOpConstant &&
+               "unexpected struct index");
+        uint32_t member_idx = curr_idx_inst->GetSingleWordInOperand(0);
+        uint32_t member_offset = 0xdeadbeef;
+        bool found = !get_decoration_mgr()->WhileEachDecoration(
+            curr_ty_id, SpvDecorationOffset,
+            [&member_idx, &member_offset](const Instruction& deco_inst) {
+              if (deco_inst.GetSingleWordInOperand(1u) != member_idx)
+                return true;
+              member_offset = deco_inst.GetSingleWordInOperand(3u);
+              return false;
+            });
+        USE_ASSERT(found && "member offset not found");
+        curr_offset_id = builder->GetUintConstantId(member_offset);
+        // Get element type for next step
+        curr_ty_id = curr_ty_inst->GetSingleWordInOperand(member_idx);
+      } break;
+      default: { assert(false && "unexpected non-composite type"); } break;
+    }
+    if (sum_id == 0)
+      sum_id = curr_offset_id;
+    else {
+      Instruction* sum_inst =
+          builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, curr_offset_id);
+      sum_id = sum_inst->result_id();
+    }
+    ++ac_in_idx;
+  }
+  // Add in offset of last byte of referenced object
+  uint32_t bsize = ByteSize(curr_ty_id);
+  uint32_t last = bsize - 1;
+  uint32_t last_id = builder->GetUintConstantId(last);
+  Instruction* sum_inst =
+      builder->AddBinaryOp(GetUintId(), SpvOpIAdd, sum_id, last_id);
+  return sum_inst->result_id();
+}
+
 void InstBindlessCheckPass::GenCheckCode(
-    uint32_t check_id, uint32_t error_id, uint32_t length_id,
-    uint32_t stage_idx, ref_analysis* ref,
+    uint32_t check_id, uint32_t error_id, uint32_t offset_id,
+    uint32_t length_id, uint32_t stage_idx, ref_analysis* ref,
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
   BasicBlock* back_blk_ptr = &*new_blocks->back();
   InstructionBuilder builder(
@@ -279,9 +435,28 @@
   // Gen invalid block
   new_blk_ptr.reset(new BasicBlock(std::move(invalid_label)));
   builder.SetInsertPoint(&*new_blk_ptr);
-  uint32_t u_index_id = GenUintCastCode(ref->index_id, &builder);
-  GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
-                      {error_id, u_index_id, length_id}, &builder);
+  uint32_t u_index_id = GenUintCastCode(ref->desc_idx_id, &builder);
+  if (offset_id != 0) {
+    // Buffer OOB
+    uint32_t u_offset_id = GenUintCastCode(offset_id, &builder);
+    uint32_t u_length_id = GenUintCastCode(length_id, &builder);
+    GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
+                        {error_id, u_index_id, u_offset_id, u_length_id},
+                        &builder);
+  } else if (buffer_bounds_enabled_) {
+    // Uninitialized Descriptor - Return additional unused zero so all error
+    // modes will use same debug stream write function
+    uint32_t u_length_id = GenUintCastCode(length_id, &builder);
+    GenDebugStreamWrite(
+        uid2offset_[ref->ref_inst->unique_id()], stage_idx,
+        {error_id, u_index_id, u_length_id, builder.GetUintConstantId(0)},
+        &builder);
+  } else {
+    // Uninitialized Descriptor - Normal error return
+    uint32_t u_length_id = GenUintCastCode(length_id, &builder);
+    GenDebugStreamWrite(uid2offset_[ref->ref_inst->unique_id()], stage_idx,
+                        {error_id, u_index_id, u_length_id}, &builder);
+  }
   // Remember last invalid block id
   uint32_t last_invalid_blk_id = new_blk_ptr->GetLabelInst()->result_id();
   // Gen zero for invalid  reference
@@ -305,7 +480,7 @@
   context()->KillInst(ref->ref_inst);
 }
 
-void InstBindlessCheckPass::GenBoundsCheckCode(
+void InstBindlessCheckPass::GenDescIdxCheckCode(
     BasicBlock::iterator ref_inst_itr,
     UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
@@ -318,19 +493,19 @@
   // If index and bound both compile-time constants and index < bound,
   // return without changing
   Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
-  Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
+  Instruction* desc_type_inst = GetPointeeTypeInst(var_inst);
   uint32_t length_id = 0;
   if (desc_type_inst->opcode() == SpvOpTypeArray) {
     length_id =
         desc_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
-    Instruction* index_inst = get_def_use_mgr()->GetDef(ref.index_id);
+    Instruction* index_inst = get_def_use_mgr()->GetDef(ref.desc_idx_id);
     Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
     if (index_inst->opcode() == SpvOpConstant &&
         length_inst->opcode() == SpvOpConstant &&
         index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
             length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
       return;
-  } else if (!input_length_enabled_ ||
+  } else if (!desc_idx_enabled_ ||
              desc_type_inst->opcode() != SpvOpTypeRuntimeArray) {
     return;
   }
@@ -352,9 +527,12 @@
   // Generate full runtime bounds test code with true branch
   // being full reference and false branch being debug output and zero
   // for the referenced value.
-  Instruction* ult_inst =
-      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref.index_id, length_id);
-  GenCheckCode(ult_inst->result_id(), error_id, length_id, stage_idx, &ref,
+  uint32_t desc_idx_32b_id = Gen32BitCvtCode(ref.desc_idx_id, &builder);
+  uint32_t length_32b_id = Gen32BitCvtCode(length_id, &builder);
+  Instruction* ult_inst = builder.AddBinaryOp(GetBoolId(), SpvOpULessThan,
+                                              desc_idx_32b_id, length_32b_id);
+  ref.desc_idx_id = desc_idx_32b_id;
+  GenCheckCode(ult_inst->result_id(), error_id, 0u, length_id, stage_idx, &ref,
                new_blocks);
   // Move original block's remaining code into remainder/merge block and add
   // to new blocks
@@ -362,13 +540,30 @@
   MovePostludeCode(ref_block_itr, back_blk_ptr);
 }
 
-void InstBindlessCheckPass::GenInitCheckCode(
+void InstBindlessCheckPass::GenDescInitCheckCode(
     BasicBlock::iterator ref_inst_itr,
     UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
     std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
   // Look for reference through descriptor. If not, return.
   ref_analysis ref;
   if (!AnalyzeDescriptorReference(&*ref_inst_itr, &ref)) return;
+  // Determine if we can only do initialization check
+  bool init_check = false;
+  if (ref.desc_load_id != 0 || !buffer_bounds_enabled_) {
+    init_check = true;
+  } else {
+    // For now, only do bounds check for non-aggregate types. Otherwise
+    // just do descriptor initialization check.
+    // TODO(greg-lunarg): Do bounds check for aggregate loads and stores
+    Instruction* ref_ptr_inst = get_def_use_mgr()->GetDef(ref.ptr_id);
+    Instruction* pte_type_inst = GetPointeeTypeInst(ref_ptr_inst);
+    uint32_t pte_type_op = pte_type_inst->opcode();
+    if (pte_type_op == SpvOpTypeArray || pte_type_op == SpvOpTypeRuntimeArray ||
+        pte_type_op == SpvOpTypeStruct)
+      init_check = true;
+  }
+  // If initialization check and not enabled, return
+  if (init_check && !desc_init_enabled_) return;
   // Move original block's preceding instructions into first new block
   std::unique_ptr<BasicBlock> new_blk_ptr;
   MovePreludeCode(ref_inst_itr, ref_block_itr, &new_blk_ptr);
@@ -376,19 +571,25 @@
       context(), &*new_blk_ptr,
       IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
   new_blocks->push_back(std::move(new_blk_ptr));
-  // Read initialization status from debug input buffer. If index id not yet
+  // If initialization check, use reference value of zero.
+  // Else use the index of the last byte referenced.
+  uint32_t ref_id = init_check ? builder.GetUintConstantId(0u)
+                               : GenLastByteIdx(&ref, &builder);
+  // Read initialization/bounds from debug input buffer. If index id not yet
   // set, binding is single descriptor, so set index to constant 0.
-  uint32_t zero_id = builder.GetUintConstantId(0u);
-  if (ref.index_id == 0) ref.index_id = zero_id;
-  uint32_t init_id = GenDebugReadInit(ref.var_id, ref.index_id, &builder);
-  // Generate full runtime non-zero init test code with true branch
+  if (ref.desc_idx_id == 0) ref.desc_idx_id = builder.GetUintConstantId(0u);
+  uint32_t init_id = GenDebugReadInit(ref.var_id, ref.desc_idx_id, &builder);
+  // Generate runtime initialization/bounds test code with true branch
   // being full reference and false branch being debug output and zero
   // for the referenced value.
-  Instruction* uneq_inst =
-      builder.AddBinaryOp(GetBoolId(), SpvOpINotEqual, init_id, zero_id);
-  uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessUninit);
-  GenCheckCode(uneq_inst->result_id(), error_id, zero_id, stage_idx, &ref,
-               new_blocks);
+  Instruction* ult_inst =
+      builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, ref_id, init_id);
+  uint32_t error =
+      init_check ? kInstErrorBindlessUninit : kInstErrorBindlessBuffOOB;
+  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,
+               &ref, new_blocks);
   // Move original block's remaining code into remainder/merge block and add
   // to new blocks
   BasicBlock* back_blk_ptr = &*new_blocks->back();
@@ -400,7 +601,7 @@
   InitializeInstrument();
   // If runtime array length support enabled, create variable mappings. Length
   // support is always enabled if descriptor init check is enabled.
-  if (input_length_enabled_)
+  if (desc_idx_enabled_ || buffer_bounds_enabled_)
     for (auto& anno : get_module()->annotations())
       if (anno.opcode() == SpvOpDecorate) {
         if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
@@ -418,19 +619,19 @@
       [this](BasicBlock::iterator ref_inst_itr,
              UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
              std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-        return GenBoundsCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
-                                  new_blocks);
+        return GenDescIdxCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                   new_blocks);
       };
   bool modified = InstProcessEntryPointCallTree(pfn);
-  if (input_init_enabled_) {
+  if (desc_init_enabled_ || buffer_bounds_enabled_) {
     // Perform descriptor initialization check on each entry point function in
     // module
     pfn = [this](BasicBlock::iterator ref_inst_itr,
                  UptrVectorIterator<BasicBlock> ref_block_itr,
                  uint32_t stage_idx,
                  std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
-      return GenInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
-                              new_blocks);
+      return GenDescInitCheckCode(ref_inst_itr, ref_block_itr, stage_idx,
+                                  new_blocks);
     };
     modified |= InstProcessEntryPointCallTree(pfn);
   }
diff --git a/source/opt/inst_bindless_check_pass.h b/source/opt/inst_bindless_check_pass.h
index 9335fa5..50dfd95 100644
--- a/source/opt/inst_bindless_check_pass.h
+++ b/source/opt/inst_bindless_check_pass.h
@@ -28,12 +28,24 @@
 // external design may change as the layer evolves.
 class InstBindlessCheckPass : public InstrumentPass {
  public:
-  // Preferred Interface
+  // Old interface to support testing pre-buffer-overrun capability
   InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
-                        bool input_length_enable, bool input_init_enable)
-      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
-        input_length_enabled_(input_length_enable),
-        input_init_enabled_(input_init_enable) {}
+                        bool desc_idx_enable, bool desc_init_enable)
+      : InstrumentPass(desc_set, shader_id, kInstValidationIdBindless, false),
+        desc_idx_enabled_(desc_idx_enable),
+        desc_init_enabled_(desc_init_enable),
+        buffer_bounds_enabled_(false) {}
+
+  // New interface supporting buffer overrun checking
+  InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
+                        bool desc_idx_enable, bool desc_init_enable,
+                        bool buffer_bounds_enable)
+      : InstrumentPass(
+            desc_set, shader_id, kInstValidationIdBindless,
+            desc_idx_enable || desc_init_enable || buffer_bounds_enable),
+        desc_idx_enabled_(desc_idx_enable),
+        desc_init_enabled_(desc_init_enable),
+        buffer_bounds_enabled_(buffer_bounds_enable) {}
 
   ~InstBindlessCheckPass() override = default;
 
@@ -46,13 +58,11 @@
   // These functions do bindless checking instrumentation on a single
   // instruction which references through a descriptor (ie references into an
   // image or buffer). Refer to Vulkan API for further information on
-  // descriptors. GenBoundsCheckCode checks that an index into a descriptor
-  // array (array of images or buffers) is in-bounds. GenInitCheckCode
+  // descriptors. GenDescIdxCheckCode checks that an index into a descriptor
+  // array (array of images or buffers) is in-bounds. GenDescInitCheckCode
   // checks that the referenced descriptor has been initialized, if the
-  // SPV_EXT_descriptor_indexing extension is enabled.
-  //
-  // TODO(greg-lunarg): Add support for buffers. Currently only does
-  // checking of references of images.
+  // SPV_EXT_descriptor_indexing extension is enabled, and initialized large
+  // enough to handle the reference, if RobustBufferAccess is disabled.
   //
   // The functions are designed to be passed to
   // InstrumentPass::InstProcessEntryPointCallTree(), which applies the
@@ -89,15 +99,15 @@
   //
   // The Descriptor Array Size is the size of the descriptor array which was
   // indexed.
-  void GenBoundsCheckCode(BasicBlock::iterator ref_inst_itr,
-                          UptrVectorIterator<BasicBlock> ref_block_itr,
-                          uint32_t stage_idx,
-                          std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+  void GenDescIdxCheckCode(
+      BasicBlock::iterator ref_inst_itr,
+      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+      std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
-  void GenInitCheckCode(BasicBlock::iterator ref_inst_itr,
-                        UptrVectorIterator<BasicBlock> ref_block_itr,
-                        uint32_t stage_idx,
-                        std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+  void GenDescInitCheckCode(
+      BasicBlock::iterator ref_inst_itr,
+      UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t stage_idx,
+      std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
   // Generate instructions into |builder| to read length of runtime descriptor
   // array |var_id| from debug input buffer and return id of value.
@@ -118,10 +128,20 @@
     uint32_t load_id;
     uint32_t ptr_id;
     uint32_t var_id;
-    uint32_t index_id;
+    uint32_t desc_idx_id;
     Instruction* ref_inst;
   } ref_analysis;
 
+  // Return size of type |ty_id| in bytes.
+  uint32_t ByteSize(uint32_t ty_id);
+
+  // Return stride of type |ty_id| with decoration |stride_deco|. Return 0
+  // if not found
+  uint32_t FindStride(uint32_t ty_id, uint32_t stride_deco);
+
+  // Generate index of last byte referenced by buffer reference |ref|
+  uint32_t GenLastByteIdx(ref_analysis* ref, InstructionBuilder* builder);
+
   // Clone original original reference encapsulated by |ref| into |builder|.
   // This may generate more than one instruction if neccessary.
   uint32_t CloneOriginalReference(ref_analysis* ref,
@@ -131,8 +151,8 @@
   // references through. Else return 0.
   uint32_t GetImageId(Instruction* inst);
 
-  // Get descriptor type inst of variable |var_inst|.
-  Instruction* GetDescriptorTypeInst(Instruction* var_inst);
+  // Get pointee type inst of pointer value |ptr_inst|.
+  Instruction* GetPointeeTypeInst(Instruction* ptr_inst);
 
   // Analyze descriptor reference |ref_inst| and save components into |ref|.
   // Return true if |ref_inst| is a descriptor reference, false otherwise.
@@ -145,22 +165,25 @@
   // writes debug error output utilizing |ref|, |error_id|, |length_id| and
   // |stage_idx|. Generate merge block for valid and invalid branches. Kill
   // original reference.
-  void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t length_id,
-                    uint32_t stage_idx, ref_analysis* ref,
+  void GenCheckCode(uint32_t check_id, uint32_t error_id, uint32_t offset_id,
+                    uint32_t length_id, uint32_t stage_idx, ref_analysis* ref,
                     std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
 
   // Initialize state for instrumenting bindless checking
   void InitializeInstBindlessCheck();
 
-  // Apply GenBoundsCheckCode to every instruction in module. Then apply
-  // GenInitCheckCode to every instruction in module.
+  // Apply GenDescIdxCheckCode to every instruction in module. Then apply
+  // GenDescInitCheckCode to every instruction in module.
   Pass::Status ProcessImpl();
 
   // Enable instrumentation of runtime array length checking
-  bool input_length_enabled_;
+  bool desc_idx_enabled_;
 
   // Enable instrumentation of descriptor initialization checking
-  bool input_init_enabled_;
+  bool desc_init_enabled_;
+
+  // Enable instrumentation of buffer overrun checking
+  bool buffer_bounds_enabled_;
 
   // Mapping from variable to descriptor set
   std::unordered_map<uint32_t, uint32_t> var2desc_set_;
diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp
index 126848e..1054a20 100644
--- a/source/opt/instruction.cpp
+++ b/source/opt/instruction.cpp
@@ -183,8 +183,9 @@
     std::vector<uint32_t>* binary) const {
   const uint32_t num_words = 1 + NumOperandWords();
   binary->push_back((num_words << 16) | static_cast<uint16_t>(opcode_));
-  for (const auto& operand : operands_)
+  for (const auto& operand : operands_) {
     binary->insert(binary->end(), operand.words.begin(), operand.words.end());
+  }
 }
 
 void Instruction::ReplaceOperands(const OperandList& new_operands) {
@@ -283,8 +284,7 @@
 
   // Check if the image is sampled.  If we do not know for sure that it is,
   // then assume it is a storage image.
-  auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex);
-  return s != 1;
+  return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) != 1;
 }
 
 bool Instruction::IsVulkanSampledImage() const {
@@ -318,8 +318,7 @@
 
   // Check if the image is sampled.  If we know for sure that it is,
   // then return true.
-  auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex);
-  return s == 1;
+  return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) == 1;
 }
 
 bool Instruction::IsVulkanStorageTexelBuffer() const {
@@ -502,16 +501,50 @@
   return subtype;
 }
 
-Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& i) {
-  i.get()->InsertBefore(this);
-  return i.release();
+void Instruction::UpdateLexicalScope(uint32_t scope) {
+  dbg_scope_.SetLexicalScope(scope);
+  for (auto& i : dbg_line_insts_) {
+    i.dbg_scope_.SetLexicalScope(scope);
+  }
+  if (!IsDebugLineInst(opcode()) &&
+      context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) {
+    context()->get_debug_info_mgr()->AnalyzeDebugInst(this);
+  }
+}
+
+void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) {
+  dbg_scope_.SetInlinedAt(new_inlined_at);
+  for (auto& i : dbg_line_insts_) {
+    i.dbg_scope_.SetInlinedAt(new_inlined_at);
+  }
+  if (!IsDebugLineInst(opcode()) &&
+      context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) {
+    context()->get_debug_info_mgr()->AnalyzeDebugInst(this);
+  }
+}
+
+void Instruction::UpdateDebugInfoFrom(const Instruction* from) {
+  if (from == nullptr) return;
+  clear_dbg_line_insts();
+  if (!from->dbg_line_insts().empty())
+    dbg_line_insts().push_back(from->dbg_line_insts().back());
+  SetDebugScope(from->GetDebugScope());
+  if (!IsDebugLineInst(opcode()) &&
+      context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) {
+    context()->get_debug_info_mgr()->AnalyzeDebugInst(this);
+  }
+}
+
+Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& inst) {
+  inst.get()->InsertBefore(this);
+  return inst.release();
 }
 
 Instruction* Instruction::InsertBefore(
     std::vector<std::unique_ptr<Instruction>>&& list) {
   Instruction* first_node = list.front().get();
-  for (auto& i : list) {
-    i.release()->InsertBefore(this);
+  for (auto& inst : list) {
+    inst.release()->InsertBefore(this);
   }
   list.clear();
   return first_node;
@@ -568,10 +601,13 @@
 }
 
 OpenCLDebugInfo100Instructions Instruction::GetOpenCL100DebugOpcode() const {
-  if (opcode() != SpvOpExtInst) return OpenCLDebugInfo100InstructionsMax;
-
-  if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo())
+  if (opcode() != SpvOpExtInst) {
     return OpenCLDebugInfo100InstructionsMax;
+  }
+
+  if (!context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) {
+    return OpenCLDebugInfo100InstructionsMax;
+  }
 
   if (GetSingleWordInOperand(kExtInstSetIdInIdx) !=
       context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo()) {
@@ -622,6 +658,7 @@
   if (!folder.IsFoldableOpcode(opcode())) {
     return false;
   }
+
   Instruction* type = context()->get_def_use_mgr()->GetDef(type_id());
   if (!folder.IsFoldableType(type)) {
     return false;
@@ -889,6 +926,16 @@
   }
 }
 
+bool Instruction::IsNonSemanticInstruction() const {
+  if (!HasResultId()) return false;
+  if (opcode() != SpvOpExtInst) return false;
+
+  auto import_inst =
+      context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(0));
+  std::string import_name = import_inst->GetInOperand(0).AsString();
+  return import_name.find("NonSemantic.") == 0;
+}
+
 void DebugScope::ToBinary(uint32_t type_id, uint32_t result_id,
                           uint32_t ext_set,
                           std::vector<uint32_t>* binary) const {
@@ -908,8 +955,10 @@
       static_cast<uint32_t>(dbg_opcode),
   };
   binary->insert(binary->end(), operands.begin(), operands.end());
-  if (GetLexicalScope() != kNoDebugScope) binary->push_back(GetLexicalScope());
-  if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt());
+  if (GetLexicalScope() != kNoDebugScope) {
+    binary->push_back(GetLexicalScope());
+    if (GetInlinedAt() != kNoInlinedAt) binary->push_back(GetInlinedAt());
+  }
 }
 
 }  // namespace opt
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index e99a5ae..252e8cb 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -246,6 +246,11 @@
   // 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);
@@ -296,12 +301,14 @@
   inline void SetDebugScope(const DebugScope& scope);
   inline const DebugScope& GetDebugScope() const { return dbg_scope_; }
   // Updates DebugInlinedAt of DebugScope and OpLine.
-  inline void UpdateDebugInlinedAt(uint32_t new_inlined_at);
+  void UpdateDebugInlinedAt(uint32_t new_inlined_at);
   inline uint32_t GetDebugInlinedAt() const {
     return dbg_scope_.GetInlinedAt();
   }
+  // Updates lexical scope of DebugScope and OpLine.
+  void UpdateLexicalScope(uint32_t scope);
   // Updates OpLine and DebugScope based on the information of |from|.
-  inline void UpdateDebugInfo(const Instruction* from);
+  void UpdateDebugInfoFrom(const Instruction* from);
   // Remove the |index|-th operand
   void RemoveOperand(uint32_t index) {
     operands_.erase(operands_.begin() + index);
@@ -544,6 +551,9 @@
     return GetOpenCL100DebugOpcode() != OpenCLDebugInfo100InstructionsMax;
   }
 
+  // Returns true if this instructions a non-semantic instruction.
+  bool IsNonSemanticInstruction() const;
+
   // Dump this instruction on stderr.  Useful when running interactive
   // debuggers.
   void Dump() const;
@@ -660,21 +670,6 @@
   }
 }
 
-inline void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) {
-  dbg_scope_.SetInlinedAt(new_inlined_at);
-  for (auto& i : dbg_line_insts_) {
-    i.dbg_scope_.SetInlinedAt(new_inlined_at);
-  }
-}
-
-inline void Instruction::UpdateDebugInfo(const Instruction* from) {
-  if (from == nullptr) return;
-  clear_dbg_line_insts();
-  if (!from->dbg_line_insts().empty())
-    dbg_line_insts().push_back(from->dbg_line_insts()[0]);
-  SetDebugScope(from->GetDebugScope());
-}
-
 inline void Instruction::SetResultType(uint32_t ty_id) {
   // TODO(dsinclair): Allow setting a type id if there wasn't one
   // previously. Need to make room in the operands_ array to place the result,
@@ -744,21 +739,21 @@
 }
 
 inline void Instruction::ForEachId(const std::function<void(uint32_t*)>& f) {
-  for (auto& opnd : operands_)
-    if (spvIsIdType(opnd.type)) f(&opnd.words[0]);
+  for (auto& operand : operands_)
+    if (spvIsIdType(operand.type)) f(&operand.words[0]);
 }
 
 inline void Instruction::ForEachId(
     const std::function<void(const uint32_t*)>& f) const {
-  for (const auto& opnd : operands_)
-    if (spvIsIdType(opnd.type)) f(&opnd.words[0]);
+  for (const auto& operand : operands_)
+    if (spvIsIdType(operand.type)) f(&operand.words[0]);
 }
 
 inline bool Instruction::WhileEachInId(
     const std::function<bool(uint32_t*)>& f) {
-  for (auto& opnd : operands_) {
-    if (spvIsInIdType(opnd.type)) {
-      if (!f(&opnd.words[0])) return false;
+  for (auto& operand : operands_) {
+    if (spvIsInIdType(operand.type) && !f(&operand.words[0])) {
+      return false;
     }
   }
   return true;
@@ -766,9 +761,9 @@
 
 inline bool Instruction::WhileEachInId(
     const std::function<bool(const uint32_t*)>& f) const {
-  for (const auto& opnd : operands_) {
-    if (spvIsInIdType(opnd.type)) {
-      if (!f(&opnd.words[0])) return false;
+  for (const auto& operand : operands_) {
+    if (spvIsInIdType(operand.type) && !f(&operand.words[0])) {
+      return false;
     }
   }
   return true;
@@ -791,13 +786,13 @@
 
 inline bool Instruction::WhileEachInOperand(
     const std::function<bool(uint32_t*)>& f) {
-  for (auto& opnd : operands_) {
-    switch (opnd.type) {
+  for (auto& operand : operands_) {
+    switch (operand.type) {
       case SPV_OPERAND_TYPE_RESULT_ID:
       case SPV_OPERAND_TYPE_TYPE_ID:
         break;
       default:
-        if (!f(&opnd.words[0])) return false;
+        if (!f(&operand.words[0])) return false;
         break;
     }
   }
@@ -806,13 +801,13 @@
 
 inline bool Instruction::WhileEachInOperand(
     const std::function<bool(const uint32_t*)>& f) const {
-  for (const auto& opnd : operands_) {
-    switch (opnd.type) {
+  for (const auto& operand : operands_) {
+    switch (operand.type) {
       case SPV_OPERAND_TYPE_RESULT_ID:
       case SPV_OPERAND_TYPE_TYPE_ID:
         break;
       default:
-        if (!f(&opnd.words[0])) return false;
+        if (!f(&operand.words[0])) return false;
         break;
     }
   }
@@ -821,16 +816,16 @@
 
 inline void Instruction::ForEachInOperand(
     const std::function<void(uint32_t*)>& f) {
-  WhileEachInOperand([&f](uint32_t* op) {
-    f(op);
+  WhileEachInOperand([&f](uint32_t* operand) {
+    f(operand);
     return true;
   });
 }
 
 inline void Instruction::ForEachInOperand(
     const std::function<void(const uint32_t*)>& f) const {
-  WhileEachInOperand([&f](const uint32_t* op) {
-    f(op);
+  WhileEachInOperand([&f](const uint32_t* operand) {
+    f(operand);
     return true;
   });
 }
diff --git a/source/opt/instrument_pass.cpp b/source/opt/instrument_pass.cpp
index e6a55a8..e7d9778 100644
--- a/source/opt/instrument_pass.cpp
+++ b/source/opt/instrument_pass.cpp
@@ -88,12 +88,36 @@
   return newLabel;
 }
 
+uint32_t InstrumentPass::Gen32BitCvtCode(uint32_t val_id,
+                                         InstructionBuilder* builder) {
+  // Convert integer value to 32-bit if necessary
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_id)->type_id();
+  analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger();
+  if (val_ty->width() == 32) return val_id;
+  bool is_signed = val_ty->IsSigned();
+  analysis::Integer val_32b_ty(32, is_signed);
+  analysis::Type* val_32b_reg_ty = type_mgr->GetRegisteredType(&val_32b_ty);
+  uint32_t val_32b_reg_ty_id = type_mgr->GetId(val_32b_reg_ty);
+  if (is_signed)
+    return builder->AddUnaryOp(val_32b_reg_ty_id, SpvOpSConvert, val_id)
+        ->result_id();
+  else
+    return builder->AddUnaryOp(val_32b_reg_ty_id, SpvOpUConvert, val_id)
+        ->result_id();
+}
+
 uint32_t InstrumentPass::GenUintCastCode(uint32_t val_id,
                                          InstructionBuilder* builder) {
-  // Cast value to 32-bit unsigned if necessary
-  if (get_def_use_mgr()->GetDef(val_id)->type_id() == GetUintId())
-    return val_id;
-  return builder->AddUnaryOp(GetUintId(), SpvOpBitcast, val_id)->result_id();
+  // Convert value to 32-bit if necessary
+  uint32_t val_32b_id = Gen32BitCvtCode(val_id, builder);
+  // Cast value to unsigned if necessary
+  analysis::TypeManager* type_mgr = context()->get_type_mgr();
+  uint32_t val_ty_id = get_def_use_mgr()->GetDef(val_32b_id)->type_id();
+  analysis::Integer* val_ty = type_mgr->GetType(val_ty_id)->AsInteger();
+  if (!val_ty->IsSigned()) return val_32b_id;
+  return builder->AddUnaryOp(GetUintId(), SpvOpBitcast, val_32b_id)
+      ->result_id();
 }
 
 void InstrumentPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
@@ -281,14 +305,42 @@
   (void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
 }
 
+bool InstrumentPass::AllConstant(const std::vector<uint32_t>& ids) {
+  for (auto& id : ids) {
+    Instruction* id_inst = context()->get_def_use_mgr()->GetDef(id);
+    if (!spvOpcodeIsConstant(id_inst->opcode())) return false;
+  }
+  return true;
+}
+
 uint32_t InstrumentPass::GenDebugDirectRead(
-    const std::vector<uint32_t>& offset_ids, InstructionBuilder* builder) {
+    const std::vector<uint32_t>& offset_ids, InstructionBuilder* ref_builder) {
   // Call debug input function. Pass func_idx and offset ids as args.
   uint32_t off_id_cnt = static_cast<uint32_t>(offset_ids.size());
   uint32_t input_func_id = GetDirectReadFunctionId(off_id_cnt);
   std::vector<uint32_t> args = {input_func_id};
   (void)args.insert(args.end(), offset_ids.begin(), offset_ids.end());
-  return builder->AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+  // If optimizing direct reads and the call has already been generated,
+  // use its result
+  if (opt_direct_reads_) {
+    uint32_t res_id = call2id_[args];
+    if (res_id != 0) return res_id;
+  }
+  // If the offsets are all constants, the call can be moved to the first block
+  // of the function where its result can be reused. One example where this is
+  // profitable is for uniform buffer references, of which there are often many.
+  InstructionBuilder builder(ref_builder->GetContext(),
+                             &*ref_builder->GetInsertPoint(),
+                             ref_builder->GetPreservedAnalysis());
+  bool insert_in_first_block = opt_direct_reads_ && AllConstant(offset_ids);
+  if (insert_in_first_block) {
+    Instruction* insert_before = &*curr_func_->begin()->tail();
+    builder.SetInsertPoint(insert_before);
+  }
+  uint32_t res_id =
+      builder.AddNaryOp(GetUintId(), SpvOpFunctionCall, args)->result_id();
+  if (insert_in_first_block) call2id_[args] = res_id;
+  return res_id;
 }
 
 bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
@@ -819,21 +871,52 @@
   return func_id;
 }
 
+void InstrumentPass::SplitBlock(
+    BasicBlock::iterator inst_itr, UptrVectorIterator<BasicBlock> block_itr,
+    std::vector<std::unique_ptr<BasicBlock>>* new_blocks) {
+  // Make sure def/use analysis is done before we start moving instructions
+  // out of function
+  (void)get_def_use_mgr();
+  // Move original block's preceding instructions into first new block
+  std::unique_ptr<BasicBlock> first_blk_ptr;
+  MovePreludeCode(inst_itr, block_itr, &first_blk_ptr);
+  InstructionBuilder builder(
+      context(), &*first_blk_ptr,
+      IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+  uint32_t split_blk_id = TakeNextId();
+  std::unique_ptr<Instruction> split_label(NewLabel(split_blk_id));
+  (void)builder.AddBranch(split_blk_id);
+  new_blocks->push_back(std::move(first_blk_ptr));
+  // Move remaining instructions into split block and add to new blocks
+  std::unique_ptr<BasicBlock> split_blk_ptr(
+      new BasicBlock(std::move(split_label)));
+  MovePostludeCode(block_itr, &*split_blk_ptr);
+  new_blocks->push_back(std::move(split_blk_ptr));
+}
+
 bool InstrumentPass::InstrumentFunction(Function* func, uint32_t stage_idx,
                                         InstProcessFunction& pfn) {
+  curr_func_ = func;
+  call2id_.clear();
+  bool first_block_split = false;
   bool modified = false;
-  // Compute function index
-  uint32_t function_idx = 0;
-  for (auto fii = get_module()->begin(); fii != get_module()->end(); ++fii) {
-    if (&*fii == func) break;
-    ++function_idx;
-  }
-  std::vector<std::unique_ptr<BasicBlock>> new_blks;
+  // Apply instrumentation function to each instruction.
   // Using block iterators here because of block erasures and insertions.
+  std::vector<std::unique_ptr<BasicBlock>> new_blks;
   for (auto bi = func->begin(); bi != func->end(); ++bi) {
     for (auto ii = bi->begin(); ii != bi->end();) {
-      // Generate instrumentation if warranted
-      pfn(ii, bi, stage_idx, &new_blks);
+      // Split all executable instructions out of first block into a following
+      // block. This will allow function calls to be inserted into the first
+      // block without interfering with the instrumentation algorithm.
+      if (opt_direct_reads_ && !first_block_split) {
+        if (ii->opcode() != SpvOpVariable) {
+          SplitBlock(ii, bi, &new_blks);
+          first_block_split = true;
+        }
+      } else {
+        pfn(ii, bi, stage_idx, &new_blks);
+      }
+      // If no new code, continue
       if (new_blks.size() == 0) {
         ++ii;
         continue;
diff --git a/source/opt/instrument_pass.h b/source/opt/instrument_pass.h
index f6884d2..12b939d 100644
--- a/source/opt/instrument_pass.h
+++ b/source/opt/instrument_pass.h
@@ -82,12 +82,15 @@
  protected:
   // Create instrumentation pass for |validation_id| which utilizes descriptor
   // set |desc_set| for debug input and output buffers and writes |shader_id|
-  // into debug output records.
-  InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id)
+  // into debug output records. |opt_direct_reads| indicates that the pass
+  // will see direct input buffer reads and should prepare to optimize them.
+  InstrumentPass(uint32_t desc_set, uint32_t shader_id, uint32_t validation_id,
+                 bool opt_direct_reads = false)
       : Pass(),
         desc_set_(desc_set),
         shader_id_(shader_id),
-        validation_id_(validation_id) {}
+        validation_id_(validation_id),
+        opt_direct_reads_(opt_direct_reads) {}
 
   // Initialize state for instrumentation of module.
   void InitializeInstrument();
@@ -196,6 +199,9 @@
                            const std::vector<uint32_t>& validation_ids,
                            InstructionBuilder* builder);
 
+  // Return true if all instructions in |ids| are constants or spec constants.
+  bool AllConstant(const std::vector<uint32_t>& ids);
+
   // Generate in |builder| instructions to read the unsigned integer from the
   // input buffer specified by the offsets in |offset_ids|. Given offsets
   // o0, o1, ... oN, and input buffer ibuf, return the id for the value:
@@ -207,8 +213,12 @@
   uint32_t GenDebugDirectRead(const std::vector<uint32_t>& offset_ids,
                               InstructionBuilder* builder);
 
-  // Generate code to cast |value_id| to unsigned, if needed. Return
-  // an id to the unsigned equivalent.
+  // Generate code to convert integer |value_id| to 32bit, if needed. Return
+  // an id to the 32bit equivalent.
+  uint32_t Gen32BitCvtCode(uint32_t value_id, InstructionBuilder* builder);
+
+  // Generate code to cast integer |value_id| to 32bit unsigned, if needed.
+  // Return an id to the Uint equivalent.
   uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
 
   // Return new label.
@@ -284,6 +294,12 @@
   // if it doesn't exist.
   uint32_t GetDirectReadFunctionId(uint32_t param_cnt);
 
+  // Split block |block_itr| into two new blocks where the second block
+  // contains |inst_itr| and place in |new_blocks|.
+  void SplitBlock(BasicBlock::iterator inst_itr,
+                  UptrVectorIterator<BasicBlock> block_itr,
+                  std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
+
   // Apply instrumentation function |pfn| to every instruction in |func|.
   // If code is generated for an instruction, replace the instruction's
   // block with the new blocks that are generated. Continue processing at the
@@ -428,6 +444,29 @@
 
   // Post-instrumentation same-block op ids
   std::unordered_map<uint32_t, uint32_t> same_block_post_;
+
+  // Map function calls to result id. Clear for every function.
+  // This is for debug input reads with constant arguments that
+  // have been generated into the first block of the function.
+  // This mechanism is used to avoid multiple identical debug
+  // input buffer reads.
+  struct vector_hash_ {
+    std::size_t operator()(const std::vector<uint32_t>& v) const {
+      std::size_t hash = v.size();
+      for (auto& u : v) {
+        hash ^= u + 0x9e3779b9 + (hash << 11) + (hash >> 21);
+      }
+      return hash;
+    }
+  };
+  std::unordered_map<std::vector<uint32_t>, uint32_t, vector_hash_> call2id_;
+
+  // Function currently being instrumented
+  Function* curr_func_;
+
+  // Optimize direct debug input buffer reads. Specifically, move all such
+  // reads with constant args to first block and reuse them.
+  bool opt_direct_reads_;
 };
 
 }  // namespace opt
diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
index b0c1d2e..fe5feff 100644
--- a/source/opt/ir_builder.h
+++ b/source/opt/ir_builder.h
@@ -601,15 +601,15 @@
     return preserved_analyses_ & analysis;
   }
 
-  // Updates the def/use manager if the user requested it. If he did not request
-  // an update, this function does nothing.
+  // Updates the def/use manager if the user requested it. If an update was not
+  // requested, this function does nothing.
   inline void UpdateDefUseMgr(Instruction* insn) {
     if (IsAnalysisUpdateRequested(IRContext::kAnalysisDefUse))
       GetContext()->get_def_use_mgr()->AnalyzeInstDefUse(insn);
   }
 
-  // Updates the instruction to block analysis if the user requested it. If he
-  // did not request an update, this function does nothing.
+  // Updates the instruction to block analysis if the user requested it. If
+  // an update was not requested, this function does nothing.
   inline void UpdateInstrToBlockMapping(Instruction* insn) {
     if (IsAnalysisUpdateRequested(IRContext::kAnalysisInstrToBlockMapping) &&
         parent_)
diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp
index 0791097..3e610d7 100644
--- a/source/opt/ir_context.cpp
+++ b/source/opt/ir_context.cpp
@@ -181,6 +181,7 @@
     }
   }
   if (AreAnalysesValid(kAnalysisDebugInfo)) {
+    get_debug_info_mgr()->ClearDebugScopeAndInlinedAtUses(inst);
     get_debug_info_mgr()->ClearDebugInfo(inst);
   }
   if (type_mgr_ && IsTypeInst(inst->opcode())) {
@@ -213,6 +214,30 @@
   return next_instruction;
 }
 
+void IRContext::KillNonSemanticInfo(Instruction* inst) {
+  if (!inst->HasResultId()) return;
+  std::vector<Instruction*> work_list;
+  std::vector<Instruction*> to_kill;
+  std::unordered_set<Instruction*> seen;
+  work_list.push_back(inst);
+
+  while (!work_list.empty()) {
+    auto* i = work_list.back();
+    work_list.pop_back();
+    get_def_use_mgr()->ForEachUser(
+        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);
+          }
+        });
+  }
+
+  for (auto* dead : to_kill) {
+    KillInst(dead);
+  }
+}
+
 bool IRContext::KillDef(uint32_t id) {
   Instruction* def = get_def_use_mgr()->GetDef(id);
   if (def != nullptr) {
@@ -222,23 +247,21 @@
   return false;
 }
 
-void IRContext::KillDebugDeclareInsts(Function* fn) {
-  fn->ForEachInst([this](Instruction* inst) {
-    if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare)
-      KillInst(inst);
-  });
-}
-
 bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) {
-  return ReplaceAllUsesWithPredicate(
-      before, after, [](Instruction*, uint32_t) { return true; });
+  return ReplaceAllUsesWithPredicate(before, after,
+                                     [](Instruction*) { return true; });
 }
 
 bool IRContext::ReplaceAllUsesWithPredicate(
     uint32_t before, uint32_t after,
-    const std::function<bool(Instruction*, uint32_t)>& predicate) {
+    const std::function<bool(Instruction*)>& predicate) {
   if (before == after) return false;
 
+  if (AreAnalysesValid(kAnalysisDebugInfo)) {
+    get_debug_info_mgr()->ReplaceAllUsesInDebugScopeWithPredicate(before, after,
+                                                                  predicate);
+  }
+
   // Ensure that |after| has been registered as def.
   assert(get_def_use_mgr()->GetDef(after) &&
          "'after' is not a registered def.");
@@ -246,7 +269,7 @@
   std::vector<std::pair<Instruction*, uint32_t>> uses_to_update;
   get_def_use_mgr()->ForEachUse(
       before, [&predicate, &uses_to_update](Instruction* user, uint32_t index) {
-        if (predicate(user, index)) {
+        if (predicate(user)) {
           uses_to_update.emplace_back(user, index);
         }
       });
@@ -284,7 +307,6 @@
     }
     AnalyzeUses(user);
   }
-
   return true;
 }
 
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index 37be836..5aa25ac 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -403,8 +403,8 @@
   // instruction exists.
   Instruction* KillInst(Instruction* inst);
 
-  // Deletes DebugDeclare instructions in the given function |fn|.
-  void KillDebugDeclareInsts(Function* fn);
+  // Removes the non-semantic instruction tree that uses |inst|'s result id.
+  void KillNonSemanticInfo(Instruction* inst);
 
   // Returns true if all of the given analyses are valid.
   bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; }
@@ -418,13 +418,13 @@
   bool ReplaceAllUsesWith(uint32_t before, uint32_t after);
 
   // Replace all uses of |before| id with |after| id if those uses
-  // (instruction, operand pair) return true for |predicate|. Returns true if
+  // (instruction) return true for |predicate|. Returns true if
   // any replacement happens. This method does not kill the definition of the
   // |before| id. If |after| is the same as |before|, does nothing and return
   // false.
   bool ReplaceAllUsesWithPredicate(
       uint32_t before, uint32_t after,
-      const std::function<bool(Instruction*, uint32_t)>& predicate);
+      const std::function<bool(Instruction*)>& predicate);
 
   // Returns true if all of the analyses that are suppose to be valid are
   // actually valid.
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index acd41cd..4a44309 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -41,6 +41,7 @@
   ++inst_index_;
   const auto opcode = static_cast<SpvOp>(inst->opcode);
   if (IsDebugLineInst(opcode)) {
+    last_line_inst_.reset();
     dbg_line_info_.push_back(
         Instruction(module()->context(), *inst, last_dbg_scope_));
     return true;
@@ -90,7 +91,16 @@
 
   std::unique_ptr<Instruction> spv_inst(
       new Instruction(module()->context(), *inst, std::move(dbg_line_info_)));
-  dbg_line_info_.clear();
+  if (!spv_inst->dbg_line_insts().empty()) {
+    if (spv_inst->dbg_line_insts().back().opcode() != SpvOpNoLine) {
+      last_line_inst_ = std::unique_ptr<Instruction>(
+          spv_inst->dbg_line_insts().back().Clone(module()->context()));
+    }
+    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_);
+  }
 
   const char* src = source_.c_str();
   spv_position_t loc = {inst_index_, 0, 0};
@@ -141,6 +151,8 @@
     function_->AddBasicBlock(std::move(block_));
     block_ = nullptr;
     last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
+    last_line_inst_.reset();
+    dbg_line_info_.clear();
   } else {
     if (function_ == nullptr) {  // Outside function definition
       SPIRV_ASSERT(consumer_, block_ == nullptr);
@@ -167,13 +179,22 @@
       } else if (IsTypeInst(opcode)) {
         module_->AddType(std::move(spv_inst));
       } else if (IsConstantInst(opcode) || opcode == SpvOpVariable ||
-                 opcode == SpvOpUndef ||
-                 (opcode == SpvOpExtInst &&
-                  spvExtInstIsNonSemantic(inst->ext_inst_type))) {
+                 opcode == SpvOpUndef) {
         module_->AddGlobalValue(std::move(spv_inst));
       } else if (opcode == SpvOpExtInst &&
                  spvExtInstIsDebugInfo(inst->ext_inst_type)) {
         module_->AddExtInstDebugInfo(std::move(spv_inst));
+      } else if (opcode == SpvOpExtInst &&
+                 spvExtInstIsNonSemantic(inst->ext_inst_type)) {
+        // If there are no functions, add the non-semantic instructions to the
+        // global values. Otherwise append it to the list of the last function.
+        auto func_begin = module_->begin();
+        auto func_end = module_->end();
+        if (func_begin == func_end) {
+          module_->AddGlobalValue(std::move(spv_inst));
+        } else {
+          (--func_end)->AddNonSemanticInstruction(std::move(spv_inst));
+        }
       } else {
         Errorf(consumer_, src, loc,
                "Unhandled inst type (opcode: %d) found outside function "
diff --git a/source/opt/ir_loader.h b/source/opt/ir_loader.h
index 5079921..d0610f1 100644
--- a/source/opt/ir_loader.h
+++ b/source/opt/ir_loader.h
@@ -78,6 +78,8 @@
   std::unique_ptr<BasicBlock> block_;
   // Line related debug instructions accumulated thus far.
   std::vector<Instruction> dbg_line_info_;
+  // Line instruction that should be applied to the next instruction.
+  std::unique_ptr<Instruction> last_line_inst_;
 
   // The last DebugScope information that IrLoader::AddInstruction() handled.
   DebugScope last_dbg_scope_;
diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp
index 9b8c112..205cd7a 100644
--- a/source/opt/local_access_chain_convert_pass.cpp
+++ b/source/opt/local_access_chain_convert_pass.cpp
@@ -77,6 +77,15 @@
 bool LocalAccessChainConvertPass::ReplaceAccessChainLoad(
     const Instruction* address_inst, Instruction* original_load) {
   // Build and append load of variable in ptrInst
+  if (address_inst->NumInOperands() == 1) {
+    // An access chain with no indices is essentially a copy.  All that is
+    // needed is to propagate the address.
+    context()->ReplaceAllUsesWith(
+        address_inst->result_id(),
+        address_inst->GetSingleWordInOperand(kAccessChainPtrIdInIdx));
+    return true;
+  }
+
   std::vector<std::unique_ptr<Instruction>> new_inst;
   uint32_t varId;
   uint32_t varPteTypeId;
@@ -86,9 +95,12 @@
     return false;
   }
 
+  new_inst[0]->UpdateDebugInfoFrom(original_load);
   context()->get_decoration_mgr()->CloneDecorations(
       original_load->result_id(), ldResultId, {SpvDecorationRelaxedPrecision});
   original_load->InsertBefore(std::move(new_inst));
+  context()->get_debug_info_mgr()->AnalyzeDebugInst(
+      original_load->PreviousNode());
 
   // Rewrite |original_load| into an extract.
   Instruction::OperandList new_operands;
@@ -109,6 +121,18 @@
 bool LocalAccessChainConvertPass::GenAccessChainStoreReplacement(
     const Instruction* ptrInst, uint32_t valId,
     std::vector<std::unique_ptr<Instruction>>* newInsts) {
+  if (ptrInst->NumInOperands() == 1) {
+    // An access chain with no indices is essentially a copy.  However, we still
+    // have to create a new store because the old ones will be deleted.
+    BuildAndAppendInst(
+        SpvOpStore, 0, 0,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID,
+          {ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx)}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}}},
+        newInsts);
+    return true;
+  }
+
   // Build and append load of variable in ptrInst
   uint32_t varId;
   uint32_t varPteTypeId;
@@ -160,6 +184,10 @@
 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) {
+          return true;
+        }
         SpvOp op = user->opcode();
         if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) {
           if (!HasOnlySupportedRefs(user->result_id())) {
@@ -230,7 +258,6 @@
           Instruction* ptrInst = GetPtr(&*ii, &varId);
           if (!IsNonPtrAccessChain(ptrInst->opcode())) break;
           if (!IsTargetVar(varId)) break;
-          std::vector<std::unique_ptr<Instruction>> newInsts;
           if (!ReplaceAccessChainLoad(ptrInst, &*ii)) {
             return Status::Failure;
           }
@@ -238,19 +265,26 @@
         } break;
         case SpvOpStore: {
           uint32_t varId;
-          Instruction* ptrInst = GetPtr(&*ii, &varId);
+          Instruction* store = &*ii;
+          Instruction* ptrInst = GetPtr(store, &varId);
           if (!IsNonPtrAccessChain(ptrInst->opcode())) break;
           if (!IsTargetVar(varId)) break;
           std::vector<std::unique_ptr<Instruction>> newInsts;
-          uint32_t valId = ii->GetSingleWordInOperand(kStoreValIdInIdx);
+          uint32_t valId = store->GetSingleWordInOperand(kStoreValIdInIdx);
           if (!GenAccessChainStoreReplacement(ptrInst, valId, &newInsts)) {
             return Status::Failure;
           }
-          dead_instructions.push_back(&*ii);
+          size_t num_of_instructions_to_skip = newInsts.size() - 1;
+          dead_instructions.push_back(store);
           ++ii;
           ii = ii.InsertBefore(std::move(newInsts));
-          ++ii;
-          ++ii;
+          for (size_t i = 0; i < num_of_instructions_to_skip; ++i) {
+            ii->UpdateDebugInfoFrom(store);
+            context()->get_debug_info_mgr()->AnalyzeDebugInst(&*ii);
+            ++ii;
+          }
+          ii->UpdateDebugInfoFrom(store);
+          context()->get_debug_info_mgr()->AnalyzeDebugInst(&*ii);
           modified = true;
         } break;
         default:
@@ -346,6 +380,7 @@
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",
diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp
index bd5d751..5f35ee1 100644
--- a/source/opt/local_single_block_elim_pass.cpp
+++ b/source/opt/local_single_block_elim_pass.cpp
@@ -83,7 +83,8 @@
             auto prev_store = var2store_.find(varId);
             if (prev_store != var2store_.end() &&
                 instructions_to_save.count(prev_store->second) == 0 &&
-                !context()->get_debug_info_mgr()->IsDebugDeclared(varId)) {
+                !context()->get_debug_info_mgr()->IsVariableDebugDeclared(
+                    varId)) {
               instructions_to_kill.push_back(prev_store->second);
               modified = true;
             }
@@ -231,6 +232,7 @@
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",
diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp
index 2384107..f1b8177 100644
--- a/source/opt/local_single_store_elim_pass.cpp
+++ b/source/opt/local_single_store_elim_pass.cpp
@@ -88,6 +88,7 @@
       "SPV_AMD_gpu_shader_half_float",
       "SPV_KHR_shader_draw_parameters",
       "SPV_KHR_subgroup_vote",
+      "SPV_KHR_8bit_storage",
       "SPV_KHR_16bit_storage",
       "SPV_KHR_device_group",
       "SPV_KHR_multiview",
@@ -142,14 +143,14 @@
   // the DebugDeclare.
   uint32_t var_id = var_inst->result_id();
   if (all_rewritten &&
-      context()->get_debug_info_mgr()->IsDebugDeclared(var_id)) {
+      context()->get_debug_info_mgr()->IsVariableDebugDeclared(var_id)) {
     const analysis::Type* var_type =
         context()->get_type_mgr()->GetType(var_inst->type_id());
     const analysis::Type* store_type = var_type->AsPointer()->pointee_type();
     if (!(store_type->AsStruct() || store_type->AsArray())) {
-      context()->get_debug_info_mgr()->AddDebugValue(
-          store_inst, var_id, store_inst->GetSingleWordInOperand(1),
-          store_inst);
+      context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
+          nullptr, var_id, store_inst->GetSingleWordInOperand(1), store_inst,
+          nullptr);
       context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
     }
   }
diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp
index ed0dd28..b5b5630 100644
--- a/source/opt/loop_descriptor.cpp
+++ b/source/opt/loop_descriptor.cpp
@@ -191,14 +191,13 @@
   if (!constant) return false;
 
   if (value) {
-    const analysis::Integer* type =
-        constant->AsIntConstant()->type()->AsInteger();
-
-    if (type->IsSigned()) {
-      *value = constant->AsIntConstant()->GetS32BitValue();
-    } else {
-      *value = constant->AsIntConstant()->GetU32BitValue();
+    const analysis::Integer* type = constant->type()->AsInteger();
+    if (!type) {
+      return false;
     }
+
+    *value = type->IsSigned() ? constant->GetSignExtendedValue()
+                              : constant->GetZeroExtendedValue();
   }
 
   return true;
@@ -511,7 +510,7 @@
 }
 
 LoopDescriptor::LoopDescriptor(IRContext* context, const Function* f)
-    : loops_(), dummy_top_loop_(nullptr) {
+    : loops_(), placeholder_top_loop_(nullptr) {
   PopulateList(context, f);
 }
 
@@ -592,7 +591,7 @@
     }
   }
   for (Loop* loop : loops_) {
-    if (!loop->HasParent()) dummy_top_loop_.nested_loops_.push_back(loop);
+    if (!loop->HasParent()) placeholder_top_loop_.nested_loops_.push_back(loop);
   }
 }
 
@@ -682,22 +681,19 @@
   if (!upper_bound) return false;
 
   // Must be integer because of the opcode on the condition.
-  int64_t condition_value = 0;
+  const analysis::Integer* type = upper_bound->type()->AsInteger();
 
-  const analysis::Integer* type =
-      upper_bound->AsIntConstant()->type()->AsInteger();
-
-  if (type->width() > 32) {
+  if (!type || type->width() > 64) {
     return false;
   }
 
-  if (type->IsSigned()) {
-    condition_value = upper_bound->AsIntConstant()->GetS32BitValue();
-  } else {
-    condition_value = upper_bound->AsIntConstant()->GetU32BitValue();
-  }
+  int64_t condition_value = type->IsSigned()
+                                ? upper_bound->GetSignExtendedValue()
+                                : upper_bound->GetZeroExtendedValue();
 
   // Find the instruction which is stepping through the loop.
+  //
+  // GetInductionStepOperation returns nullptr if |step_inst| is OpConstantNull.
   Instruction* step_inst = GetInductionStepOperation(induction);
   if (!step_inst) return false;
 
@@ -986,7 +982,7 @@
 // Adds a new loop nest to the descriptor set.
 Loop* LoopDescriptor::AddLoopNest(std::unique_ptr<Loop> new_loop) {
   Loop* loop = new_loop.release();
-  if (!loop->HasParent()) dummy_top_loop_.nested_loops_.push_back(loop);
+  if (!loop->HasParent()) placeholder_top_loop_.nested_loops_.push_back(loop);
   // Iterate from inner to outer most loop, adding basic block to loop mapping
   // as we go.
   for (Loop& current_loop :
@@ -1000,7 +996,7 @@
 }
 
 void LoopDescriptor::RemoveLoop(Loop* loop) {
-  Loop* parent = loop->GetParent() ? loop->GetParent() : &dummy_top_loop_;
+  Loop* parent = loop->GetParent() ? loop->GetParent() : &placeholder_top_loop_;
   parent->nested_loops_.erase(std::find(parent->nested_loops_.begin(),
                                         parent->nested_loops_.end(), loop));
   std::for_each(
diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h
index 6e2b828..4b4f8bc 100644
--- a/source/opt/loop_descriptor.h
+++ b/source/opt/loop_descriptor.h
@@ -406,8 +406,8 @@
   // the iterators.
   bool loop_is_marked_for_removal_;
 
-  // This is only to allow LoopDescriptor::dummy_top_loop_ to add top level
-  // loops as child.
+  // This is only to allow LoopDescriptor::placeholder_top_loop_ to add top
+  // level loops as child.
   friend class LoopDescriptor;
   friend class LoopUtils;
 };
@@ -430,14 +430,14 @@
   // Disable copy constructor, to avoid double-free on destruction.
   LoopDescriptor(const LoopDescriptor&) = delete;
   // Move constructor.
-  LoopDescriptor(LoopDescriptor&& other) : dummy_top_loop_(nullptr) {
+  LoopDescriptor(LoopDescriptor&& other) : placeholder_top_loop_(nullptr) {
     // We need to take ownership of the Loop objects in the other
     // LoopDescriptor, to avoid double-free.
     loops_ = std::move(other.loops_);
     other.loops_.clear();
     basic_block_to_loop_ = std::move(other.basic_block_to_loop_);
     other.basic_block_to_loop_.clear();
-    dummy_top_loop_ = std::move(other.dummy_top_loop_);
+    placeholder_top_loop_ = std::move(other.placeholder_top_loop_);
   }
 
   // Destructor
@@ -470,25 +470,27 @@
 
   // Iterators for post order depth first traversal of the loops.
   // Inner most loops will be visited first.
-  inline iterator begin() { return iterator::begin(&dummy_top_loop_); }
-  inline iterator end() { return iterator::end(&dummy_top_loop_); }
+  inline iterator begin() { return iterator::begin(&placeholder_top_loop_); }
+  inline iterator end() { return iterator::end(&placeholder_top_loop_); }
   inline const_iterator begin() const { return cbegin(); }
   inline const_iterator end() const { return cend(); }
   inline const_iterator cbegin() const {
-    return const_iterator::begin(&dummy_top_loop_);
+    return const_iterator::begin(&placeholder_top_loop_);
   }
   inline const_iterator cend() const {
-    return const_iterator::end(&dummy_top_loop_);
+    return const_iterator::end(&placeholder_top_loop_);
   }
 
   // Iterators for pre-order depth first traversal of the loops.
   // Inner most loops will be visited first.
-  inline pre_iterator pre_begin() { return ++pre_iterator(&dummy_top_loop_); }
+  inline pre_iterator pre_begin() {
+    return ++pre_iterator(&placeholder_top_loop_);
+  }
   inline pre_iterator pre_end() { return pre_iterator(); }
   inline const_pre_iterator pre_begin() const { return pre_cbegin(); }
   inline const_pre_iterator pre_end() const { return pre_cend(); }
   inline const_pre_iterator pre_cbegin() const {
-    return ++const_pre_iterator(&dummy_top_loop_);
+    return ++const_pre_iterator(&placeholder_top_loop_);
   }
   inline const_pre_iterator pre_cend() const { return const_pre_iterator(); }
 
@@ -524,14 +526,14 @@
   void RemoveLoop(Loop* loop);
 
   void SetAsTopLoop(Loop* loop) {
-    assert(std::find(dummy_top_loop_.begin(), dummy_top_loop_.end(), loop) ==
-               dummy_top_loop_.end() &&
+    assert(std::find(placeholder_top_loop_.begin(), placeholder_top_loop_.end(),
+                     loop) == placeholder_top_loop_.end() &&
            "already registered");
-    dummy_top_loop_.nested_loops_.push_back(loop);
+    placeholder_top_loop_.nested_loops_.push_back(loop);
   }
 
-  Loop* GetDummyRootLoop() { return &dummy_top_loop_; }
-  const Loop* GetDummyRootLoop() const { return &dummy_top_loop_; }
+  Loop* GetPlaceholderRootLoop() { return &placeholder_top_loop_; }
+  const Loop* GetPlaceholderRootLoop() const { return &placeholder_top_loop_; }
 
  private:
   // TODO(dneto): This should be a vector of unique_ptr.  But VisualStudio 2013
@@ -558,8 +560,8 @@
   // objects.
   LoopContainerType loops_;
 
-  // Dummy root: this "loop" is only there to help iterators creation.
-  Loop dummy_top_loop_;
+  // Placeholder root: this "loop" is only there to help iterators creation.
+  Loop placeholder_top_loop_;
 
   std::unordered_map<uint32_t, Loop*> basic_block_to_loop_;
 
diff --git a/source/opt/loop_peeling.cpp b/source/opt/loop_peeling.cpp
index b640542..071c27c 100644
--- a/source/opt/loop_peeling.cpp
+++ b/source/opt/loop_peeling.cpp
@@ -1063,7 +1063,7 @@
   }
 
   uint32_t cast_iteration = 0;
-  // sanity check: can we fit |iteration| in a uint32_t ?
+  // Integrity check: can we fit |iteration| in a uint32_t ?
   if (static_cast<uint64_t>(iteration) < std::numeric_limits<uint32_t>::max()) {
     cast_iteration = static_cast<uint32_t>(iteration);
   }
diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp
index 40cf6bc..6cdced4 100644
--- a/source/opt/loop_unroller.cpp
+++ b/source/opt/loop_unroller.cpp
@@ -286,6 +286,9 @@
   // to be the actual value of the phi at that point.
   void LinkLastPhisToStart(Loop* loop) const;
 
+  // Kill all debug declaration instructions from |bb|.
+  void KillDebugDeclares(BasicBlock* bb);
+
   // A pointer to the IRContext. Used to add/remove instructions and for usedef
   // chains.
   IRContext* context_;
@@ -598,6 +601,20 @@
       IRContext::Analysis::kAnalysisDefUse);
 }
 
+void LoopUnrollerUtilsImpl::KillDebugDeclares(BasicBlock* bb) {
+  // We cannot kill an instruction inside BasicBlock::ForEachInst()
+  // because it will generate dangling pointers. We use |to_be_killed|
+  // to kill them after the loop.
+  std::vector<Instruction*> to_be_killed;
+
+  bb->ForEachInst([&to_be_killed, this](Instruction* inst) {
+    if (context_->get_debug_info_mgr()->IsDebugDeclare(inst)) {
+      to_be_killed.push_back(inst);
+    }
+  });
+  for (auto* inst : to_be_killed) context_->KillInst(inst);
+}
+
 // Copy a given basic block, give it a new result_id, and store the new block
 // and the id mapping in the state. |preserve_instructions| is used to determine
 // whether or not this function should edit instructions other than the
@@ -608,6 +625,9 @@
   BasicBlock* basic_block = itr->Clone(context_);
   basic_block->SetParent(itr->GetParent());
 
+  // We do not want to duplicate DebugDeclare.
+  KillDebugDeclares(basic_block);
+
   // Assign each result a new unique ID and keep a mapping of the old ids to
   // the new ones.
   AssignNewResultIds(basic_block);
@@ -674,21 +694,21 @@
   std::vector<Instruction*> inductions;
   loop->GetInductionVariables(inductions);
   for (size_t index = 0; index < inductions.size(); ++index) {
-    Instruction* master_copy = inductions[index];
+    Instruction* primary_copy = inductions[index];
 
-    assert(master_copy->result_id() != 0);
+    assert(primary_copy->result_id() != 0);
     Instruction* induction_clone =
-        state_.ids_to_new_inst[state_.new_inst[master_copy->result_id()]];
+        state_.ids_to_new_inst[state_.new_inst[primary_copy->result_id()]];
 
     state_.new_phis_.push_back(induction_clone);
     assert(induction_clone->result_id() != 0);
 
     if (!state_.previous_phis_.empty()) {
-      state_.new_inst[master_copy->result_id()] = GetPhiDefID(
+      state_.new_inst[primary_copy->result_id()] = GetPhiDefID(
           state_.previous_phis_[index], state_.previous_latch_block_->id());
     } else {
       // Do not replace the first phi block ids.
-      state_.new_inst[master_copy->result_id()] = master_copy->result_id();
+      state_.new_inst[primary_copy->result_id()] = primary_copy->result_id();
     }
   }
 
@@ -729,13 +749,19 @@
   Instruction& old_branch = *condition_block->tail();
   uint32_t new_target = old_branch.GetSingleWordOperand(operand_label);
 
+  DebugScope scope = old_branch.GetDebugScope();
+  const std::vector<Instruction> lines = old_branch.dbg_line_insts();
+
   context_->KillInst(&old_branch);
   // Add the new unconditional branch to the merge block.
   InstructionBuilder builder(
       context_, condition_block,
       IRContext::Analysis::kAnalysisDefUse |
           IRContext::Analysis::kAnalysisInstrToBlockMapping);
-  builder.AddBranch(new_target);
+  Instruction* new_branch = builder.AddBranch(new_target);
+
+  new_branch->set_dbg_line_insts(lines);
+  new_branch->SetDebugScope(scope);
 }
 
 void LoopUnrollerUtilsImpl::CloseUnrolledLoop(Loop* loop) {
diff --git a/source/opt/loop_unswitch_pass.cpp b/source/opt/loop_unswitch_pass.cpp
index 502fc6b..d805ecf 100644
--- a/source/opt/loop_unswitch_pass.cpp
+++ b/source/opt/loop_unswitch_pass.cpp
@@ -594,9 +594,9 @@
   bool loop_changed = true;
   while (loop_changed) {
     loop_changed = false;
-    for (Loop& loop :
-         make_range(++TreeDFIterator<Loop>(loop_descriptor.GetDummyRootLoop()),
-                    TreeDFIterator<Loop>())) {
+    for (Loop& loop : make_range(
+             ++TreeDFIterator<Loop>(loop_descriptor.GetPlaceholderRootLoop()),
+             TreeDFIterator<Loop>())) {
       if (processed_loop.count(&loop)) continue;
       processed_loop.insert(&loop);
 
diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp
index 2421c2c..b43eb31 100644
--- a/source/opt/merge_return_pass.cpp
+++ b/source/opt/merge_return_pass.cpp
@@ -111,7 +111,7 @@
   }
 
   RecordImmediateDominators(function);
-  AddDummySwitchAroundFunction();
+  AddSingleCaseSwitchAroundFunction();
 
   std::list<BasicBlock*> order;
   cfg()->ComputeStructuredOrder(function, &*function->begin(), &order);
@@ -223,7 +223,8 @@
 
   if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue ||
       tail_opcode == SpvOpUnreachable) {
-    assert(CurrentState().InBreakable() && "Should be in the dummy construct.");
+    assert(CurrentState().InBreakable() &&
+           "Should be in the placeholder construct.");
     BranchToBlock(block, CurrentState().BreakMergeId());
     return_blocks_.insert(block->id());
   }
@@ -408,7 +409,7 @@
     if (!predicated->insert(block).second) break;
     // Skip structured subgraphs.
     assert(state->InBreakable() &&
-           "Should be in the dummy construct at the very least.");
+           "Should be in the placeholder construct at the very least.");
     Instruction* break_merge_inst = state->BreakMergeInst();
     uint32_t merge_block_id = break_merge_inst->GetSingleWordInOperand(0);
     while (state->BreakMergeId() == merge_block_id) {
@@ -768,7 +769,7 @@
   list->insert(pos, new_element);
 }
 
-void MergeReturnPass::AddDummySwitchAroundFunction() {
+void MergeReturnPass::AddSingleCaseSwitchAroundFunction() {
   CreateReturnBlock();
   CreateReturn(final_return_block_);
 
@@ -776,7 +777,7 @@
     cfg()->RegisterBlock(final_return_block_);
   }
 
-  CreateDummySwitch(final_return_block_);
+  CreateSingleCaseSwitch(final_return_block_);
 }
 
 BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) {
@@ -811,7 +812,7 @@
   return new_block;
 }
 
-void MergeReturnPass::CreateDummySwitch(BasicBlock* merge_target) {
+void 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();
diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h
index fe85557..06a3e7b 100644
--- a/source/opt/merge_return_pass.h
+++ b/source/opt/merge_return_pass.h
@@ -48,13 +48,13 @@
  * is the final return. This block should branch to the new return block (its
  * direct successor). If the current block is within structured control flow,
  * the branch destination should be the innermost construct's merge.  This
- * merge will always exist because a dummy switch is added around the
+ * merge will always exist because a single case switch is added around the
  * entire function. If the merge block produces any live values it will need to
  * be predicated. While the merge is nested in structured control flow, the
  * predication path should branch to the merge block of the inner-most loop
  * (or switch if no loop) it is contained in. Once structured control flow has
- * been exited, it will be at the merge of the dummy switch, which will simply
- * return.
+ * been exited, it will be at the merge of the single case switch, which will
+ * simply return.
  *
  * In the final return block, the return value should be loaded and returned.
  * Memory promotion passes should be able to promote the newly introduced
@@ -73,7 +73,7 @@
  *         ||
  *         \/
  *
- *          0 (dummy switch header)
+ *          0 (single case switch header)
  *          |
  *          1 (loop header)
  *         / \
@@ -83,7 +83,7 @@
  *        / \
  *        |  3 (original code in 3)
  *        \ /
- *   (ret) 4 (dummy switch merge)
+ *   (ret) 4 (single case switch merge)
  *
  * In the above (simple) example, the return originally in |2| is passed through
  * the loop merge. That merge is predicated such that the old body of the block
@@ -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 AddDummySwitchAroundFunction();
+  void 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 CreateDummySwitch(BasicBlock* merge_target);
+  void 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 2959d3d..9d3b0ed 100644
--- a/source/opt/module.cpp
+++ b/source/opt/module.cpp
@@ -98,7 +98,10 @@
   DELEGATE(ext_inst_debuginfo_);
   DELEGATE(annotations_);
   DELEGATE(types_values_);
-  for (auto& i : functions_) i->ForEachInst(f, run_on_debug_line_insts);
+  for (auto& i : functions_) {
+    i->ForEachInst(f, run_on_debug_line_insts,
+                   /* run_on_non_semantic_insts = */ true);
+  }
 #undef DELEGATE
 }
 
@@ -120,8 +123,9 @@
   for (auto& i : types_values_) DELEGATE(i);
   for (auto& i : ext_inst_debuginfo_) DELEGATE(i);
   for (auto& i : functions_) {
-    static_cast<const Function*>(i.get())->ForEachInst(f,
-                                                       run_on_debug_line_insts);
+    static_cast<const Function*>(i.get())->ForEachInst(
+        f, run_on_debug_line_insts,
+        /* run_on_non_semantic_insts = */ true);
   }
   if (run_on_debug_line_insts) {
     for (auto& i : trailing_dbg_line_info_) DELEGATE(i);
@@ -139,8 +143,38 @@
 
   size_t bound_idx = binary->size() - 2;
   DebugScope last_scope(kNoDebugScope, kNoInlinedAt);
-  auto write_inst = [binary, skip_nop, &last_scope,
-                     this](const Instruction* i) {
+  const Instruction* last_line_inst = nullptr;
+  bool between_merge_and_branch = false;
+  auto write_inst = [binary, skip_nop, &last_scope, &last_line_inst,
+                     &between_merge_and_branch, 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)) {
+      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
+      // to the next instruction), we skip writing the current instruction.
+      if (opcode == SpvOpLine) {
+        uint32_t operand_index = 0;
+        if (last_line_inst->WhileEachInOperand(
+                [&operand_index, i](const uint32_t* word) {
+                  assert(i->NumInOperandWords() > operand_index);
+                  return *word == i->GetSingleWordInOperand(operand_index++);
+                })) {
+          return;
+        }
+      } else if (opcode != SpvOpNoLine && 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));
+        last_line_inst = nullptr;
+      }
+    }
     if (!(skip_nop && i->IsNop())) {
       const auto& scope = i->GetDebugScope();
       if (scope != last_scope) {
@@ -153,6 +187,15 @@
 
       i->ToBinaryWithoutAttachedDebugInsts(binary);
     }
+    // Update the last line instruction.
+    if (IsTerminatorInst(opcode) || opcode == SpvOpNoLine) {
+      last_line_inst = nullptr;
+    } else if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge) {
+      between_merge_and_branch = true;
+      last_line_inst = nullptr;
+    } else if (opcode == SpvOpLine) {
+      last_line_inst = i;
+    }
   };
   ForEachInst(write_inst, true);
 
diff --git a/source/opt/module.h b/source/opt/module.h
index 2c96f02..75da870 100644
--- a/source/opt/module.h
+++ b/source/opt/module.h
@@ -246,6 +246,12 @@
   // If |skip_nop| is true and this is a OpNop, do nothing.
   void ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const;
 
+  // Pushes the binary segments for this instruction into the back of *|binary|
+  // including all OpLine and OpNoLine even if we can skip emitting some line
+  // instructions. If |skip_nop| is true and this is a OpNop, do nothing.
+  void ToBinaryWithAllOpLines(std::vector<uint32_t>* binary,
+                              bool skip_nop) const;
+
   // Returns 1 more than the maximum Id value mentioned in the module.
   uint32_t ComputeIdBound() const;
 
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 25adee9..7a6a33b 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -339,10 +339,6 @@
     RegisterPass(CreateDescriptorScalarReplacementPass());
   } else if (pass_name == "eliminate-dead-code-aggressive") {
     RegisterPass(CreateAggressiveDCEPass());
-  } else if (pass_name == "propagate-line-info") {
-    RegisterPass(CreatePropagateLineInfoPass());
-  } else if (pass_name == "eliminate-redundant-line-info") {
-    RegisterPass(CreateRedundantLineInfoElimPass());
   } else if (pass_name == "eliminate-insert-extract") {
     RegisterPass(CreateInsertExtractElimPass());
   } else if (pass_name == "eliminate-local-single-block") {
@@ -427,6 +423,12 @@
     RegisterPass(CreateDeadBranchElimPass());
     RegisterPass(CreateBlockMergePass());
     RegisterPass(CreateAggressiveDCEPass());
+  } else if (pass_name == "inst-buff-oob-check") {
+    RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, true));
+    RegisterPass(CreateSimplificationPass());
+    RegisterPass(CreateDeadBranchElimPass());
+    RegisterPass(CreateBlockMergePass());
+    RegisterPass(CreateAggressiveDCEPass());
   } else if (pass_name == "inst-buff-addr-check") {
     RegisterPass(CreateInstBuffAddrCheckPass(7, 23));
     RegisterPass(CreateAggressiveDCEPass());
@@ -579,8 +581,8 @@
 
 #ifndef NDEBUG
   // We do not keep the result id of DebugScope in struct DebugScope.
-  // Instead, we assign random ids for them, which results in sanity
-  // check failures. We want to skip the sanity check when the module
+  // Instead, we assign random ids for them, which results in integrity
+  // check failures. We want to skip the integrity check when the module
   // contains DebugScope instructions.
   if (status == opt::Pass::Status::SuccessWithoutChange &&
       !context->module()->ContainsDebugScope()) {
@@ -751,16 +753,6 @@
       MakeUnique<opt::AggressiveDCEPass>());
 }
 
-Optimizer::PassToken CreatePropagateLineInfoPass() {
-  return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::ProcessLinesPass>(opt::kLinesPropagateLines));
-}
-
-Optimizer::PassToken CreateRedundantLineInfoElimPass() {
-  return MakeUnique<Optimizer::PassToken::Impl>(
-      MakeUnique<opt::ProcessLinesPass>(opt::kLinesEliminateDeadLines));
-}
-
 Optimizer::PassToken CreateCompactIdsPass() {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::CompactIdsPass>());
@@ -894,10 +886,12 @@
 Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
                                                  uint32_t shader_id,
                                                  bool input_length_enable,
-                                                 bool input_init_enable) {
+                                                 bool input_init_enable,
+                                                 bool input_buff_oob_enable) {
   return MakeUnique<Optimizer::PassToken::Impl>(
       MakeUnique<opt::InstBindlessCheckPass>(
-          desc_set, shader_id, input_length_enable, input_init_enable));
+          desc_set, shader_id, input_length_enable, input_init_enable,
+          input_buff_oob_enable));
 }
 
 Optimizer::PassToken CreateInstDebugPrintfPass(uint32_t desc_set,
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 5b4ab89..acc30e1 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -61,7 +61,6 @@
 #include "source/opt/merge_return_pass.h"
 #include "source/opt/null_pass.h"
 #include "source/opt/private_to_local_pass.h"
-#include "source/opt/process_lines_pass.h"
 #include "source/opt/reduce_load_size.h"
 #include "source/opt/redundancy_elimination.h"
 #include "source/opt/relax_float_ops_pass.h"
diff --git a/source/opt/process_lines_pass.cpp b/source/opt/process_lines_pass.cpp
deleted file mode 100644
index 0ae2f75..0000000
--- a/source/opt/process_lines_pass.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright (c) 2018 The Khronos Group Inc.
-// Copyright (c) 2018 Valve Corporation
-// Copyright (c) 2018 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/process_lines_pass.h"
-
-#include <set>
-#include <unordered_set>
-#include <vector>
-
-namespace {
-
-// Input Operand Indices
-static const int kSpvLineFileInIdx = 0;
-static const int kSpvLineLineInIdx = 1;
-static const int kSpvLineColInIdx = 2;
-
-}  // anonymous namespace
-
-namespace spvtools {
-namespace opt {
-
-Pass::Status ProcessLinesPass::Process() {
-  bool modified = ProcessLines();
-  return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
-}
-
-bool ProcessLinesPass::ProcessLines() {
-  bool modified = false;
-  uint32_t file_id = 0;
-  uint32_t line = 0;
-  uint32_t col = 0;
-  // Process types, globals, constants
-  for (Instruction& inst : get_module()->types_values())
-    modified |= line_process_func_(&inst, &file_id, &line, &col);
-  // Process functions
-  for (Function& function : *get_module()) {
-    modified |= line_process_func_(&function.DefInst(), &file_id, &line, &col);
-    function.ForEachParam(
-        [this, &modified, &file_id, &line, &col](Instruction* param) {
-          modified |= line_process_func_(param, &file_id, &line, &col);
-        });
-    for (BasicBlock& block : function) {
-      modified |=
-          line_process_func_(block.GetLabelInst(), &file_id, &line, &col);
-      for (Instruction& inst : block) {
-        modified |= line_process_func_(&inst, &file_id, &line, &col);
-        // Don't process terminal instruction if preceeded by merge
-        if (inst.opcode() == SpvOpSelectionMerge ||
-            inst.opcode() == SpvOpLoopMerge)
-          break;
-      }
-      // Nullify line info after each block.
-      file_id = 0;
-    }
-    modified |= line_process_func_(function.EndInst(), &file_id, &line, &col);
-  }
-  return modified;
-}
-
-bool ProcessLinesPass::PropagateLine(Instruction* inst, uint32_t* file_id,
-                                     uint32_t* line, uint32_t* col) {
-  bool modified = false;
-  // only the last debug instruction needs to be considered
-  auto line_itr = inst->dbg_line_insts().rbegin();
-  // if no line instructions, propagate previous info
-  if (line_itr == inst->dbg_line_insts().rend()) {
-    // if no current line info, add OpNoLine, else OpLine
-    if (*file_id == 0)
-      inst->dbg_line_insts().push_back(Instruction(context(), SpvOpNoLine));
-    else
-      inst->dbg_line_insts().push_back(Instruction(
-          context(), SpvOpLine, 0, 0,
-          {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {*file_id}},
-           {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {*line}},
-           {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {*col}}}));
-    modified = true;
-  } else {
-    // else pre-existing line instruction, so update source line info
-    if (line_itr->opcode() == SpvOpNoLine) {
-      *file_id = 0;
-    } else {
-      assert(line_itr->opcode() == SpvOpLine && "unexpected debug inst");
-      *file_id = line_itr->GetSingleWordInOperand(kSpvLineFileInIdx);
-      *line = line_itr->GetSingleWordInOperand(kSpvLineLineInIdx);
-      *col = line_itr->GetSingleWordInOperand(kSpvLineColInIdx);
-    }
-  }
-  return modified;
-}
-
-bool ProcessLinesPass::EliminateDeadLines(Instruction* inst, uint32_t* file_id,
-                                          uint32_t* line, uint32_t* col) {
-  // If no debug line instructions, return without modifying lines
-  if (inst->dbg_line_insts().empty()) return false;
-  // Only the last debug instruction needs to be considered; delete all others
-  bool modified = inst->dbg_line_insts().size() > 1;
-  Instruction last_inst = inst->dbg_line_insts().back();
-  inst->dbg_line_insts().clear();
-  // If last line is OpNoLine
-  if (last_inst.opcode() == SpvOpNoLine) {
-    // If no propagated line info, throw away redundant OpNoLine
-    if (*file_id == 0) {
-      modified = true;
-      // Else replace OpNoLine and propagate no line info
-    } else {
-      inst->dbg_line_insts().push_back(last_inst);
-      *file_id = 0;
-    }
-  } else {
-    // Else last line is OpLine
-    assert(last_inst.opcode() == SpvOpLine && "unexpected debug inst");
-    // If propagated info matches last line, throw away last line
-    if (*file_id == last_inst.GetSingleWordInOperand(kSpvLineFileInIdx) &&
-        *line == last_inst.GetSingleWordInOperand(kSpvLineLineInIdx) &&
-        *col == last_inst.GetSingleWordInOperand(kSpvLineColInIdx)) {
-      modified = true;
-    } else {
-      // Else replace last line and propagate line info
-      *file_id = last_inst.GetSingleWordInOperand(kSpvLineFileInIdx);
-      *line = last_inst.GetSingleWordInOperand(kSpvLineLineInIdx);
-      *col = last_inst.GetSingleWordInOperand(kSpvLineColInIdx);
-      inst->dbg_line_insts().push_back(last_inst);
-    }
-  }
-  return modified;
-}
-
-ProcessLinesPass::ProcessLinesPass(uint32_t func_id) {
-  if (func_id == kLinesPropagateLines) {
-    line_process_func_ = [this](Instruction* inst, uint32_t* file_id,
-                                uint32_t* line, uint32_t* col) {
-      return PropagateLine(inst, file_id, line, col);
-    };
-  } else {
-    assert(func_id == kLinesEliminateDeadLines && "unknown Lines param");
-    line_process_func_ = [this](Instruction* inst, uint32_t* file_id,
-                                uint32_t* line, uint32_t* col) {
-      return EliminateDeadLines(inst, file_id, line, col);
-    };
-  }
-}
-
-}  // namespace opt
-}  // namespace spvtools
diff --git a/source/opt/process_lines_pass.h b/source/opt/process_lines_pass.h
deleted file mode 100644
index c988bfd..0000000
--- a/source/opt/process_lines_pass.h
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2018 The Khronos Group Inc.
-// Copyright (c) 2018 Valve Corporation
-// Copyright (c) 2018 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_PROPAGATE_LINES_PASS_H_
-#define SOURCE_OPT_PROPAGATE_LINES_PASS_H_
-
-#include "source/opt/function.h"
-#include "source/opt/ir_context.h"
-#include "source/opt/pass.h"
-
-namespace spvtools {
-namespace opt {
-
-namespace {
-
-// Constructor Parameters
-static const int kLinesPropagateLines = 0;
-static const int kLinesEliminateDeadLines = 1;
-
-}  // anonymous namespace
-
-// See optimizer.hpp for documentation.
-class ProcessLinesPass : public Pass {
-  using LineProcessFunction =
-      std::function<bool(Instruction*, uint32_t*, uint32_t*, uint32_t*)>;
-
- public:
-  ProcessLinesPass(uint32_t func_id);
-  ~ProcessLinesPass() override = default;
-
-  const char* name() const override { return "propagate-lines"; }
-
-  // See optimizer.hpp for this pass' user documentation.
-  Status Process() override;
-
-  IRContext::Analysis GetPreservedAnalyses() override {
-    return IRContext::kAnalysisDefUse |
-           IRContext::kAnalysisInstrToBlockMapping |
-           IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
-           IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
-           IRContext::kAnalysisNameMap | IRContext::kAnalysisConstants |
-           IRContext::kAnalysisTypes;
-  }
-
- private:
-  // If |inst| has no debug line instruction, create one with
-  // |file_id, line, col|. If |inst| has debug line instructions, set
-  // |file_id, line, col| from the last. |file_id| equals 0 indicates no line
-  // info is available. Return true if |inst| modified.
-  bool PropagateLine(Instruction* inst, uint32_t* file_id, uint32_t* line,
-                     uint32_t* col);
-
-  // If last debug line instruction of |inst| matches |file_id, line, col|,
-  // delete all debug line instructions of |inst|. If they do not match,
-  // replace all debug line instructions of |inst| with new line instruction
-  // set from |file_id, line, col|. If |inst| has no debug line instructions,
-  // do not modify |inst|. |file_id| equals 0 indicates no line info is
-  // available. Return true if |inst| modified.
-  bool EliminateDeadLines(Instruction* inst, uint32_t* file_id, uint32_t* line,
-                          uint32_t* col);
-
-  // Apply lpfn() to all type, constant, global variable and function
-  // instructions in their physical order.
-  bool ProcessLines();
-
-  // A function that calls either PropagateLine or EliminateDeadLines.
-  // Initialized by the class constructor.
-  LineProcessFunction line_process_func_;
-};
-
-}  // namespace opt
-}  // namespace spvtools
-
-#endif  // SOURCE_OPT_PROPAGATE_LINES_PASS_H_
diff --git a/source/opt/register_pressure.cpp b/source/opt/register_pressure.cpp
index cb24674..5750c6d 100644
--- a/source/opt/register_pressure.cpp
+++ b/source/opt/register_pressure.cpp
@@ -163,7 +163,7 @@
 
   // Propagates the register liveness information of each loop iterators.
   void DoLoopLivenessUnification() {
-    for (const Loop* loop : *loop_desc_.GetDummyRootLoop()) {
+    for (const Loop* loop : *loop_desc_.GetPlaceholderRootLoop()) {
       DoLoopLivenessUnification(*loop);
     }
   }
diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp
index 36c0c0d..d71d605 100644
--- a/source/opt/scalar_replacement_pass.cpp
+++ b/source/opt/scalar_replacement_pass.cpp
@@ -25,6 +25,10 @@
 #include "source/opt/types.h"
 #include "source/util/make_unique.h"
 
+static const uint32_t kDebugDeclareOperandLocalVariableIndex = 4;
+static const uint32_t kDebugValueOperandValueIndex = 5;
+static const uint32_t kDebugValueOperandExpressionIndex = 6;
+
 namespace spvtools {
 namespace opt {
 
@@ -80,6 +84,20 @@
   std::vector<Instruction*> dead;
   bool replaced_all_uses = get_def_use_mgr()->WhileEachUser(
       inst, [this, &replacements, &dead](Instruction* user) {
+        if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugDeclare) {
+          if (ReplaceWholeDebugDeclare(user, replacements)) {
+            dead.push_back(user);
+            return true;
+          }
+          return false;
+        }
+        if (user->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugValue) {
+          if (ReplaceWholeDebugValue(user, replacements)) {
+            dead.push_back(user);
+            return true;
+          }
+          return false;
+        }
         if (!IsAnnotationInst(user->opcode())) {
           switch (user->opcode()) {
             case SpvOpLoad:
@@ -144,6 +162,58 @@
   return Status::SuccessWithChange;
 }
 
+bool ScalarReplacementPass::ReplaceWholeDebugDeclare(
+    Instruction* dbg_decl, const std::vector<Instruction*>& replacements) {
+  // Insert Deref operation to the front of the operation list of |dbg_decl|.
+  Instruction* dbg_expr = context()->get_def_use_mgr()->GetDef(
+      dbg_decl->GetSingleWordOperand(kDebugValueOperandExpressionIndex));
+  auto* deref_expr =
+      context()->get_debug_info_mgr()->DerefDebugExpression(dbg_expr);
+
+  // Add DebugValue instruction with Indexes operand and Deref operation.
+  int32_t idx = 0;
+  for (const auto* var : replacements) {
+    uint32_t dbg_local_variable =
+        dbg_decl->GetSingleWordOperand(kDebugDeclareOperandLocalVariableIndex);
+    uint32_t index_id = context()->get_constant_mgr()->GetSIntConst(idx);
+
+    Instruction* added_dbg_value =
+        context()->get_debug_info_mgr()->AddDebugValueWithIndex(
+            dbg_local_variable,
+            /*value_id=*/var->result_id(), /*expr_id=*/deref_expr->result_id(),
+            index_id, /*insert_before=*/var->NextNode());
+    if (added_dbg_value == nullptr) return false;
+    added_dbg_value->UpdateDebugInfoFrom(dbg_decl);
+    ++idx;
+  }
+  return true;
+}
+
+bool ScalarReplacementPass::ReplaceWholeDebugValue(
+    Instruction* dbg_value, const std::vector<Instruction*>& replacements) {
+  int32_t idx = 0;
+  BasicBlock* block = context()->get_instr_block(dbg_value);
+  for (auto var : replacements) {
+    // Clone the DebugValue.
+    std::unique_ptr<Instruction> new_dbg_value(dbg_value->Clone(context()));
+    uint32_t new_id = TakeNextId();
+    if (new_id == 0) return false;
+    new_dbg_value->SetResultId(new_id);
+    // Update 'Value' operand to the |replacements|.
+    new_dbg_value->SetOperand(kDebugValueOperandValueIndex, {var->result_id()});
+    // Append 'Indexes' operand.
+    new_dbg_value->AddOperand(
+        {SPV_OPERAND_TYPE_ID,
+         {context()->get_constant_mgr()->GetSIntConst(idx)}});
+    // Insert the new DebugValue to the basic block.
+    auto* added_instr = dbg_value->InsertBefore(std::move(new_dbg_value));
+    get_def_use_mgr()->AnalyzeInstDefUse(added_instr);
+    context()->set_instr_block(added_instr, block);
+    ++idx;
+  }
+  return true;
+}
+
 bool ScalarReplacementPass::ReplaceWholeLoad(
     Instruction* load, const std::vector<Instruction*>& replacements) {
   // Replaces the load of the entire composite with a load from each replacement
@@ -177,6 +247,7 @@
     where = where.InsertBefore(std::move(newLoad));
     get_def_use_mgr()->AnalyzeInstDefUse(&*where);
     context()->set_instr_block(&*where, block);
+    where->UpdateDebugInfoFrom(load);
     loads.push_back(&*where);
   }
 
@@ -195,6 +266,7 @@
   }
   where = where.InsertBefore(std::move(compositeConstruct));
   get_def_use_mgr()->AnalyzeInstDefUse(&*where);
+  where->UpdateDebugInfoFrom(load);
   context()->set_instr_block(&*where, block);
   context()->ReplaceAllUsesWith(load->result_id(), compositeId);
   return true;
@@ -226,6 +298,7 @@
             {SPV_OPERAND_TYPE_ID, {storeInput}},
             {SPV_OPERAND_TYPE_LITERAL_INTEGER, {elementIndex++}}}));
     auto iter = where.InsertBefore(std::move(extract));
+    iter->UpdateDebugInfoFrom(store);
     get_def_use_mgr()->AnalyzeInstDefUse(&*iter);
     context()->set_instr_block(&*iter, block);
 
@@ -242,6 +315,7 @@
       newStore->AddOperand(std::move(copy));
     }
     iter = where.InsertBefore(std::move(newStore));
+    iter->UpdateDebugInfoFrom(store);
     get_def_use_mgr()->AnalyzeInstDefUse(&*iter);
     context()->set_instr_block(&*iter, block);
   }
@@ -281,6 +355,7 @@
         Operand copy(chain->GetInOperand(i));
         replacementChain->AddOperand(std::move(copy));
       }
+      replacementChain->UpdateDebugInfoFrom(chain);
       auto iter = chainIter.InsertBefore(std::move(replacementChain));
       get_def_use_mgr()->AnalyzeInstDefUse(&*iter);
       context()->set_instr_block(&*iter, context()->get_instr_block(chain));
@@ -427,6 +502,9 @@
     }
   }
 
+  // Update the OpenCL.DebugInfo.100 debug information.
+  inst->UpdateDebugInfoFrom(varInst);
+
   replacements->push_back(inst);
 }
 
@@ -711,6 +789,14 @@
   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) {
+      // TODO: include num_partial_accesses if it uses Fragment operation or
+      // DebugValue has Indexes operand.
+      stats->num_full_accesses++;
+      return;
+    }
+
     // Annotations are check as a group separately.
     if (!IsAnnotationInst(user->opcode())) {
       switch (user->opcode()) {
diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h
index e20f1f1..1f6c928 100644
--- a/source/opt/scalar_replacement_pass.h
+++ b/source/opt/scalar_replacement_pass.h
@@ -199,6 +199,21 @@
   bool ReplaceWholeStore(Instruction* store,
                          const std::vector<Instruction*>& replacements);
 
+  // Replaces the DebugDeclare to the entire composite.
+  //
+  // Generates a DebugValue with Deref operation for each element in the
+  // scalarized variable from the original DebugDeclare.  Returns true if
+  // successful.
+  bool ReplaceWholeDebugDeclare(Instruction* dbg_decl,
+                                const std::vector<Instruction*>& replacements);
+
+  // Replaces the DebugValue to the entire composite.
+  //
+  // Generates a DebugValue for each element in the scalarized variable from
+  // the original DebugValue.  Returns true if successful.
+  bool ReplaceWholeDebugValue(Instruction* dbg_value,
+                              const std::vector<Instruction*>& replacements);
+
   // Replaces an access chain to the composite variable with either a direct use
   // of the appropriate replacement variable or another access chain with the
   // replacement variable as the base and one fewer indexes. Returns true if
diff --git a/source/opt/simplification_pass.cpp b/source/opt/simplification_pass.cpp
index 001f354..319ceec 100644
--- a/source/opt/simplification_pass.cpp
+++ b/source/opt/simplification_pass.cpp
@@ -90,7 +90,7 @@
             if (inst->opcode() == SpvOpCopyObject) {
               context()->ReplaceAllUsesWithPredicate(
                   inst->result_id(), inst->GetSingleWordInOperand(0),
-                  [](Instruction* user, uint32_t) {
+                  [](Instruction* user) {
                     const auto opcode = user->opcode();
                     if (!spvOpcodeIsDebug(opcode) &&
                         !spvOpcodeIsDecoration(opcode)) {
@@ -137,7 +137,7 @@
       if (inst->opcode() == SpvOpCopyObject) {
         context()->ReplaceAllUsesWithPredicate(
             inst->result_id(), inst->GetSingleWordInOperand(0),
-            [](Instruction* user, uint32_t) {
+            [](Instruction* user) {
               const auto opcode = user->opcode();
               if (!spvOpcodeIsDebug(opcode) && !spvOpcodeIsDecoration(opcode)) {
                 return true;
diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp
index 1477db4..5a56887 100644
--- a/source/opt/ssa_rewrite_pass.cpp
+++ b/source/opt/ssa_rewrite_pass.cpp
@@ -66,6 +66,7 @@
 namespace {
 const uint32_t kStoreValIdInIdx = 1;
 const uint32_t kVariableInitIdInIdx = 1;
+const uint32_t kDebugDeclareOperandVariableIdx = 5;
 }  // namespace
 
 std::string SSARewriter::PhiCandidate::PrettyPrint(const CFG* cfg) const {
@@ -241,8 +242,8 @@
   return repl_id;
 }
 
-uint32_t SSARewriter::GetReachingDef(uint32_t var_id, BasicBlock* bb) {
-  // If |var_id| has a definition in |bb|, return it.
+uint32_t SSARewriter::GetValueAtBlock(uint32_t var_id, BasicBlock* bb) {
+  assert(bb != nullptr);
   const auto& bb_it = defs_at_block_.find(bb);
   if (bb_it != defs_at_block_.end()) {
     const auto& current_defs = bb_it->second;
@@ -251,9 +252,15 @@
       return var_it->second;
     }
   }
+  return 0;
+}
+
+uint32_t SSARewriter::GetReachingDef(uint32_t var_id, BasicBlock* bb) {
+  // If |var_id| has a definition in |bb|, return it.
+  uint32_t val_id = GetValueAtBlock(var_id, bb);
+  if (val_id != 0) return val_id;
 
   // Otherwise, look up the value for |var_id| in |bb|'s predecessors.
-  uint32_t val_id = 0;
   auto& predecessors = pass_->cfg()->preds(bb->id());
   if (predecessors.size() == 1) {
     // If |bb| has exactly one predecessor, we look for |var_id|'s definition
@@ -307,8 +314,8 @@
   }
   if (pass_->IsTargetVar(var_id)) {
     WriteVariable(var_id, bb, val_id);
-    pass_->context()->get_debug_info_mgr()->AddDebugValue(inst, var_id, val_id,
-                                                          inst);
+    pass_->context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
+        inst, var_id, val_id, inst, &decls_invisible_to_value_assignment_);
 
 #if SSA_REWRITE_DEBUGGING_LEVEL > 1
     std::cerr << "\tFound store '%" << var_id << " = %" << val_id << "': "
@@ -439,8 +446,6 @@
 
   // Add Phi instructions from completed Phi candidates.
   std::vector<Instruction*> generated_phis;
-  // Add DebugValue instructions for Phi instructions.
-  std::vector<Instruction*> dbg_values_for_phis;
   for (const PhiCandidate* phi_candidate : phis_to_generate_) {
 #if SSA_REWRITE_DEBUGGING_LEVEL > 2
     std::cerr << "Phi candidate: " << phi_candidate->PrettyPrint(pass_->cfg())
@@ -491,9 +496,9 @@
 
     // Add DebugValue for the new OpPhi instruction.
     insert_it->SetDebugScope(local_var->GetDebugScope());
-    pass_->context()->get_debug_info_mgr()->AddDebugValue(
+    pass_->context()->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(
         &*insert_it, phi_candidate->var_id(), phi_candidate->result_id(),
-        &*insert_it);
+        &*insert_it, &decls_invisible_to_value_assignment_);
 
     modified = true;
   }
@@ -581,6 +586,61 @@
   }
 }
 
+Pass::Status SSARewriter::AddDebugValuesForInvisibleDebugDecls(Function* fp) {
+  // For the cases the value assignment is invisible to DebugDeclare e.g.,
+  // the argument passing for an inlined function.
+  //
+  // Before inlining foo(int x):
+  //   a = 3;
+  //   foo(3);
+  // After inlining:
+  //   a = 3; // we want to specify "DebugValue: %x = %int_3"
+  //   foo and x disappeared!
+  //
+  // We want to specify the value for the variable using |defs_at_block_[bb]|,
+  // where |bb| is the basic block contains the decl.
+  DominatorAnalysis* dom_tree = pass_->context()->GetDominatorAnalysis(fp);
+  Pass::Status status = Pass::Status::SuccessWithoutChange;
+  for (auto* decl : decls_invisible_to_value_assignment_) {
+    uint32_t var_id =
+        decl->GetSingleWordOperand(kDebugDeclareOperandVariableIdx);
+    auto* var = pass_->get_def_use_mgr()->GetDef(var_id);
+    if (var->opcode() == SpvOpFunctionParameter) continue;
+
+    auto* bb = pass_->context()->get_instr_block(decl);
+    uint32_t value_id = GetValueAtBlock(var_id, bb);
+    Instruction* value = nullptr;
+    if (value_id) value = pass_->get_def_use_mgr()->GetDef(value_id);
+
+    // If |value| is defined before the function body, it dominates |decl|.
+    // If |value| dominates |decl|, we can set it as DebugValue.
+    if (value && (pass_->context()->get_instr_block(value) == nullptr ||
+                  dom_tree->Dominates(value, decl))) {
+      if (!pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
+              decl, value->result_id())) {
+        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 &&
+          !pass_->context()->get_debug_info_mgr()->AddDebugValueForDecl(
+              decl, value_id)) {
+        return Pass::Status::Failure;
+      }
+    }
+
+    // DebugDeclares of target variables will be removed by
+    // SSARewritePass::Process().
+    if (!pass_->IsTargetVar(var_id)) {
+      pass_->context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
+    }
+    status = Pass::Status::SuccessWithChange;
+  }
+  return status;
+}
+
 Pass::Status SSARewriter::RewriteFunctionIntoSSA(Function* fp) {
 #if SSA_REWRITE_DEBUGGING_LEVEL > 0
   std::cerr << "Function before SSA rewrite:\n"
@@ -610,13 +670,17 @@
   // Finally, apply all the replacements in the IR.
   bool modified = ApplyReplacements();
 
+  auto status = AddDebugValuesForInvisibleDebugDecls(fp);
+  if (status == Pass::Status::SuccessWithChange ||
+      status == Pass::Status::Failure) {
+    return status;
+  }
+
 #if SSA_REWRITE_DEBUGGING_LEVEL > 0
   std::cerr << "\n\n\nFunction after SSA rewrite:\n"
             << fp->PrettyPrint(0) << "\n";
 #endif
 
-  if (modified) pass_->context()->KillDebugDeclareInsts(fp);
-
   return modified ? Pass::Status::SuccessWithChange
                   : Pass::Status::SuccessWithoutChange;
 }
@@ -626,6 +690,10 @@
   for (auto& fn : *get_module()) {
     status =
         CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn));
+    // Kill DebugDeclares for target variables.
+    for (auto var_id : seen_target_vars_) {
+      context()->get_debug_info_mgr()->KillDebugDeclares(var_id);
+    }
     if (status == Status::Failure) {
       break;
     }
diff --git a/source/opt/ssa_rewrite_pass.h b/source/opt/ssa_rewrite_pass.h
index bbbfebb..1f4cd24 100644
--- a/source/opt/ssa_rewrite_pass.h
+++ b/source/opt/ssa_rewrite_pass.h
@@ -192,6 +192,10 @@
     }
   }
 
+  // Returns the value of |var_id| at |bb| if |defs_at_block_| contains it.
+  // Otherwise, returns 0.
+  uint32_t GetValueAtBlock(uint32_t var_id, BasicBlock* bb);
+
   // Processes the store operation |inst| in basic block |bb|. This extracts
   // the variable ID being stored into, determines whether the variable is an
   // SSA-target variable, and, if it is, it stores its value in the
@@ -249,6 +253,11 @@
   // candidates.
   void FinalizePhiCandidates();
 
+  // Adds DebugValues for DebugDeclares in
+  // |decls_invisible_to_value_assignment_|. Returns whether the function was
+  // modified or not, and whether or not the conversion was successful.
+  Pass::Status AddDebugValuesForInvisibleDebugDecls(Function* fp);
+
   // Prints the table of Phi candidates to std::cerr.
   void PrintPhiCandidates() const;
 
@@ -286,6 +295,10 @@
 
   // Memory pass requesting the SSA rewriter.
   MemPass* pass_;
+
+  // Set of DebugDeclare instructions that are not added as DebugValue because
+  // they are invisible to the store or phi instructions.
+  std::unordered_set<Instruction*> decls_invisible_to_value_assignment_;
 };
 
 class SSARewritePass : public MemPass {
diff --git a/source/opt/struct_cfg_analysis.cpp b/source/opt/struct_cfg_analysis.cpp
index 57fc49c..203db87 100644
--- a/source/opt/struct_cfg_analysis.cpp
+++ b/source/opt/struct_cfg_analysis.cpp
@@ -128,6 +128,19 @@
   return merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
 }
 
+uint32_t StructuredCFGAnalysis::NestingDepth(uint32_t bb_id) {
+  uint32_t result = 0;
+
+  // Find the merge block of the current merge construct as long as the block is
+  // inside a merge construct, exiting one for each iteration.
+  for (uint32_t merge_block_id = MergeBlock(bb_id); merge_block_id != 0;
+       merge_block_id = MergeBlock(merge_block_id)) {
+    result++;
+  }
+
+  return result;
+}
+
 uint32_t StructuredCFGAnalysis::LoopMergeBlock(uint32_t bb_id) {
   uint32_t header_id = ContainingLoop(bb_id);
   if (header_id == 0) {
@@ -150,6 +163,19 @@
   return merge_inst->GetSingleWordInOperand(kContinueNodeIndex);
 }
 
+uint32_t StructuredCFGAnalysis::LoopNestingDepth(uint32_t bb_id) {
+  uint32_t result = 0;
+
+  // Find the merge block of the current loop as long as the block is inside a
+  // loop, exiting a loop for each iteration.
+  for (uint32_t merge_block_id = LoopMergeBlock(bb_id); merge_block_id != 0;
+       merge_block_id = LoopMergeBlock(merge_block_id)) {
+    result++;
+  }
+
+  return result;
+}
+
 uint32_t StructuredCFGAnalysis::SwitchMergeBlock(uint32_t bb_id) {
   uint32_t header_id = ContainingSwitch(bb_id);
   if (header_id == 0) {
diff --git a/source/opt/struct_cfg_analysis.h b/source/opt/struct_cfg_analysis.h
index dfae6d4..9436b4f 100644
--- a/source/opt/struct_cfg_analysis.h
+++ b/source/opt/struct_cfg_analysis.h
@@ -53,6 +53,11 @@
   // merge construct.
   uint32_t MergeBlock(uint32_t bb_id);
 
+  // Returns the nesting depth of the given block, i.e. the number of merge
+  // constructs containing it. Headers and merge blocks are not considered part
+  // of the corresponding merge constructs.
+  uint32_t NestingDepth(uint32_t block_id);
+
   // Returns the id of the header of the innermost loop construct
   // that contains |bb_id|.  Return |0| if |bb_id| is not contained in any loop
   // construct.
@@ -74,6 +79,13 @@
   // construct.
   uint32_t LoopContinueBlock(uint32_t bb_id);
 
+  // Returns the loop nesting depth of |bb_id| within its function, i.e. the
+  // number of loop constructs in which |bb_id| is contained. As per other
+  // functions in StructuredCFGAnalysis, a loop header is not regarded as being
+  // part of the loop that it heads, so that e.g. the nesting depth of an
+  // outer-most loop header is 0.
+  uint32_t LoopNestingDepth(uint32_t bb_id);
+
   // Returns the id of the header of the innermost switch construct
   // that contains |bb_id| as long as there is no intervening loop.  Returns |0|
   // if no such construct exists.
diff --git a/source/opt/value_number_table.cpp b/source/opt/value_number_table.cpp
index 82549a6..32d6de9 100644
--- a/source/opt/value_number_table.cpp
+++ b/source/opt/value_number_table.cpp
@@ -49,7 +49,8 @@
   // have its own value number.
   // 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)) {
+  if (!context()->IsCombinatorInstruction(inst) &&
+      !inst->IsOpenCL100DebugInstr()) {
     value = TakeNextValueNumber();
     id_to_value_[inst->result_id()] = value;
     return value;
@@ -173,6 +174,12 @@
     }
   }
 
+  for (auto& inst : context()->module()->ext_inst_debuginfo()) {
+    if (inst.result_id() != 0) {
+      AssignValueNumber(&inst);
+    }
+  }
+
   for (Function& func : *context()->module()) {
     // For best results we want to traverse the code in reverse post order.
     // This happens naturally because of the forward referencing rules.
diff --git a/source/opt/wrap_opkill.cpp b/source/opt/wrap_opkill.cpp
index 4d70840..ae1000c 100644
--- a/source/opt/wrap_opkill.cpp
+++ b/source/opt/wrap_opkill.cpp
@@ -71,7 +71,7 @@
   if (call_inst == nullptr) {
     return false;
   }
-  call_inst->UpdateDebugInfo(inst);
+  call_inst->UpdateDebugInfoFrom(inst);
 
   Instruction* return_inst = nullptr;
   uint32_t return_type_id = GetOwningFunctionsReturnType(inst);
diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt
index d945bd2..a3291c7 100644
--- a/source/reduce/CMakeLists.txt
+++ b/source/reduce/CMakeLists.txt
@@ -50,6 +50,7 @@
         operand_to_dominating_id_reduction_opportunity_finder.cpp
         reducer.cpp
         reduction_opportunity.cpp
+        reduction_opportunity_finder.cpp
         reduction_pass.cpp
         reduction_util.cpp
         remove_block_reduction_opportunity.cpp
@@ -70,14 +71,14 @@
         simple_conditional_branch_to_branch_reduction_opportunity.cpp
 )
 
-if(MSVC)
+if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")))
   # Enable parallel builds across four cores for this lib
   add_definitions(/MP4)
 endif()
 
 spvtools_pch(SPIRV_TOOLS_REDUCE_SOURCES pch_source_reduce)
 
-add_library(SPIRV-Tools-reduce ${SPIRV_TOOLS_REDUCE_SOURCES})
+add_library(SPIRV-Tools-reduce ${SPIRV_TOOLS_LIBRARY_TYPE} ${SPIRV_TOOLS_REDUCE_SOURCES})
 
 spvtools_default_compile_options(SPIRV-Tools-reduce)
 target_include_directories(SPIRV-Tools-reduce
@@ -89,7 +90,7 @@
 )
 # The reducer reuses a lot of functionality from the SPIRV-Tools library.
 target_link_libraries(SPIRV-Tools-reduce
-  PUBLIC ${SPIRV_TOOLS}
+  PUBLIC ${SPIRV_TOOLS_FULL_VISIBILITY}
   PUBLIC SPIRV-Tools-opt)
 
 set_property(TARGET SPIRV-Tools-reduce PROPERTY FOLDER "SPIRV-Tools libraries")
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
index 0bd93b9..2cd779a 100644
--- a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
@@ -20,12 +20,10 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 ConditionalBranchToSimpleConditionalBranchOpportunityFinder::
-    GetAvailableOpportunities(IRContext* context) const {
+    GetAvailableOpportunities(opt::IRContext* context,
+                              uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Find the opportunities for redirecting all false targets before the
@@ -34,12 +32,12 @@
   // reducer is improved by avoiding contiguous opportunities that disable one
   // another.
   for (bool redirect_to_true : {true, false}) {
-    // Consider every function.
-    for (auto& function : *context->module()) {
+    // Consider every relevant function.
+    for (auto* function : GetTargetFunctions(context, target_function)) {
       // Consider every block in the function.
-      for (auto& block : function) {
+      for (auto& block : *function) {
         // The terminator must be SpvOpBranchConditional.
-        Instruction* terminator = block.terminator();
+        opt::Instruction* terminator = block.terminator();
         if (terminator->opcode() != SpvOpBranchConditional) {
           continue;
         }
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
index c582a88..17af9b0 100644
--- a/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h
@@ -26,7 +26,7 @@
     : public ReductionOpportunityFinder {
  public:
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const override;
+      opt::IRContext* context, uint32_t target_function) const override;
 
   std::string GetName() const override;
 };
diff --git a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
index d744773..8304c30 100644
--- a/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
+++ b/source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp
@@ -19,13 +19,10 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 ConditionalBranchToSimpleConditionalBranchReductionOpportunity::
     ConditionalBranchToSimpleConditionalBranchReductionOpportunity(
-        IRContext* context, Instruction* conditional_branch_instruction,
-        bool redirect_to_true)
+        opt::IRContext* context,
+        opt::Instruction* conditional_branch_instruction, bool redirect_to_true)
     : context_(context),
       conditional_branch_instruction_(conditional_branch_instruction),
       redirect_to_true_(redirect_to_true) {}
@@ -63,7 +60,8 @@
       context_->cfg()->block(old_successor_block_id));
 
   // We have changed the CFG.
-  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+  context_->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 }  // namespace reduce
diff --git a/source/reduce/merge_blocks_reduction_opportunity.cpp b/source/reduce/merge_blocks_reduction_opportunity.cpp
index 42c7843..a2c3b40 100644
--- a/source/reduce/merge_blocks_reduction_opportunity.cpp
+++ b/source/reduce/merge_blocks_reduction_opportunity.cpp
@@ -20,12 +20,8 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::Function;
-using opt::IRContext;
-
 MergeBlocksReductionOpportunity::MergeBlocksReductionOpportunity(
-    IRContext* context, Function* function, BasicBlock* block) {
+    opt::IRContext* context, opt::Function* function, opt::BasicBlock* block) {
   // Precondition: the terminator has to be OpBranch.
   assert(block->terminator()->opcode() == SpvOpBranch);
   context_ = context;
@@ -49,7 +45,8 @@
          "For a successor to be merged into its predecessor, exactly one "
          "predecessor must be present.");
   const uint32_t predecessor_id = predecessors[0];
-  BasicBlock* predecessor_block = context_->get_instr_block(predecessor_id);
+  opt::BasicBlock* predecessor_block =
+      context_->get_instr_block(predecessor_id);
   return opt::blockmergeutil::CanMergeWithSuccessor(context_,
                                                     predecessor_block);
 }
@@ -70,7 +67,8 @@
     if (bi->id() == predecessor_id) {
       opt::blockmergeutil::MergeWithSuccessor(context_, function_, bi);
       // Block merging changes the control flow graph, so invalidate it.
-      context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+      context_->InvalidateAnalysesExceptFor(
+          opt::IRContext::Analysis::kAnalysisNone);
       return;
     }
   }
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
index 89d6263..ea5e9da 100644
--- a/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.cpp
@@ -19,25 +19,23 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 std::string MergeBlocksReductionOpportunityFinder::GetName() const {
   return "MergeBlocksReductionOpportunityFinder";
 }
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 MergeBlocksReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Consider every block in every function.
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       // See whether it is possible to merge this block with its successor.
       if (opt::blockmergeutil::CanMergeWithSuccessor(context, &block)) {
         // It is, so record an opportunity to do this.
         result.push_back(spvtools::MakeUnique<MergeBlocksReductionOpportunity>(
-            context, &function, &block));
+            context, function, &block));
       }
     }
   }
diff --git a/source/reduce/merge_blocks_reduction_opportunity_finder.h b/source/reduce/merge_blocks_reduction_opportunity_finder.h
index dbf82fe..df7a8bf 100644
--- a/source/reduce/merge_blocks_reduction_opportunity_finder.h
+++ b/source/reduce/merge_blocks_reduction_opportunity_finder.h
@@ -31,7 +31,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };
diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.cpp b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
index 3e0a224..eb7498a 100644
--- a/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.cpp
@@ -20,11 +20,9 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 OperandToConstReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
   assert(result.empty());
 
@@ -37,8 +35,8 @@
   // contiguous blocks of opportunities early on, and we want to avoid having a
   // large block of incompatible opportunities if possible.
   for (const auto& constant : context->GetConstants()) {
-    for (auto& function : *context->module()) {
-      for (auto& block : function) {
+    for (auto* function : GetTargetFunctions(context, target_function)) {
+      for (auto& block : *function) {
         for (auto& inst : block) {
           // We iterate through the operands using an explicit index (rather
           // than using a lambda) so that we use said index in the construction
diff --git a/source/reduce/operand_to_const_reduction_opportunity_finder.h b/source/reduce/operand_to_const_reduction_opportunity_finder.h
index 93c0dcd..6726746 100644
--- a/source/reduce/operand_to_const_reduction_opportunity_finder.h
+++ b/source/reduce/operand_to_const_reduction_opportunity_finder.h
@@ -33,7 +33,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };
diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
index 13beb89..ca3a99e 100644
--- a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp
@@ -20,13 +20,9 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::Function;
-using opt::IRContext;
-using opt::Instruction;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 OperandToDominatingIdReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Go through every instruction in every block, considering it as a potential
@@ -42,15 +38,15 @@
   // to prioritise replacing e with its smallest sub-expressions; generalising
   // this idea to dominating ids this roughly corresponds to more distant
   // dominators.
-  for (auto& function : *context->module()) {
-    for (auto dominating_block = function.begin();
-         dominating_block != function.end(); ++dominating_block) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto dominating_block = function->begin();
+         dominating_block != function->end(); ++dominating_block) {
       for (auto& dominating_inst : *dominating_block) {
         if (dominating_inst.HasResultId() && dominating_inst.type_id()) {
           // Consider replacing any operand with matching type in a dominated
           // instruction with the id generated by this instruction.
           GetOpportunitiesForDominatingInst(
-              &result, &dominating_inst, dominating_block, &function, context);
+              &result, &dominating_inst, dominating_block, function, context);
         }
       }
     }
@@ -61,9 +57,9 @@
 void OperandToDominatingIdReductionOpportunityFinder::
     GetOpportunitiesForDominatingInst(
         std::vector<std::unique_ptr<ReductionOpportunity>>* opportunities,
-        Instruction* candidate_dominator,
-        Function::iterator candidate_dominator_block, Function* function,
-        IRContext* context) const {
+        opt::Instruction* candidate_dominator,
+        opt::Function::iterator candidate_dominator_block,
+        opt::Function* function, opt::IRContext* context) const {
   assert(candidate_dominator->HasResultId());
   assert(candidate_dominator->type_id());
   auto dominator_analysis = context->GetDominatorAnalysis(function);
@@ -91,8 +87,8 @@
             // constant.  It is thus not relevant to this pass.
             continue;
           }
-          // Sanity check that we don't get here if the argument is a constant.
-          assert(!context->get_constant_mgr()->GetConstantFromInst(def));
+          assert(!context->get_constant_mgr()->GetConstantFromInst(def) &&
+                 "We should not get here if the argument is a constant.");
           if (def->type_id() != candidate_dominator->type_id()) {
             // The types need to match.
             continue;
diff --git a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
index 7745ff7..5f33370 100644
--- a/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
+++ b/source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h
@@ -40,7 +40,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   void GetOpportunitiesForDominatingInst(
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
index 579b7df..06bf955 100644
--- a/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.cpp
@@ -20,15 +20,13 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 OperandToUndefReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       for (auto& inst : block) {
         // Skip instructions that result in a pointer type.
         auto type_id = inst.type_id();
diff --git a/source/reduce/operand_to_undef_reduction_opportunity_finder.h b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
index 9cdd8cd..a5c759e 100644
--- a/source/reduce/operand_to_undef_reduction_opportunity_finder.h
+++ b/source/reduce/operand_to_undef_reduction_opportunity_finder.h
@@ -32,7 +32,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };
diff --git a/source/reduce/reducer.cpp b/source/reduce/reducer.cpp
index 092d409..18eeaeb 100644
--- a/source/reduce/reducer.cpp
+++ b/source/reduce/reducer.cpp
@@ -183,7 +183,8 @@
       consumer_(SPV_MSG_INFO, nullptr, {},
                 ("Trying pass " + pass->GetName() + ".").c_str());
       do {
-        auto maybe_result = pass->TryApplyReduction(*current_binary);
+        auto maybe_result =
+            pass->TryApplyReduction(*current_binary, options->target_function);
         if (maybe_result.empty()) {
           // For this round, the pass has no more opportunities (chunks) to
           // apply, so move on to the next pass.
diff --git a/source/reduce/reduction_opportunity_finder.cpp b/source/reduce/reduction_opportunity_finder.cpp
new file mode 100644
index 0000000..0bd253b
--- /dev/null
+++ b/source/reduce/reduction_opportunity_finder.cpp
@@ -0,0 +1,34 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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 "reduction_opportunity_finder.h"
+
+namespace spvtools {
+namespace reduce {
+
+std::vector<opt::Function*> ReductionOpportunityFinder::GetTargetFunctions(
+    opt::IRContext* ir_context, uint32_t target_function) {
+  std::vector<opt::Function*> result;
+  for (auto& function : *ir_context->module()) {
+    if (!target_function || function.result_id() == target_function) {
+      result.push_back(&function);
+    }
+  }
+  assert((!target_function || !result.empty()) &&
+         "Requested target function must exist.");
+  return result;
+}
+
+}  // namespace reduce
+}  // namespace spvtools
diff --git a/source/reduce/reduction_opportunity_finder.h b/source/reduce/reduction_opportunity_finder.h
index 1837484..d95c832 100644
--- a/source/reduce/reduction_opportunity_finder.h
+++ b/source/reduce/reduction_opportunity_finder.h
@@ -15,6 +15,8 @@
 #ifndef SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
 #define SOURCE_REDUCE_REDUCTION_OPPORTUNITY_FINDER_H_
 
+#include <vector>
+
 #include "source/opt/ir_context.h"
 #include "source/reduce/reduction_opportunity.h"
 
@@ -29,12 +31,25 @@
   virtual ~ReductionOpportunityFinder() = default;
 
   // Finds and returns the reduction opportunities relevant to this pass that
-  // could be applied to the given SPIR-V module.
+  // could be applied to SPIR-V module |context|.
+  //
+  // If |target_function| is non-zero then the available opportunities will be
+  // restricted to only those opportunities that modify the function with result
+  // id |target_function|.
   virtual std::vector<std::unique_ptr<ReductionOpportunity>>
-  GetAvailableOpportunities(opt::IRContext* context) const = 0;
+  GetAvailableOpportunities(opt::IRContext* context,
+                            uint32_t target_function) const = 0;
 
   // Provides a name for the finder.
   virtual std::string GetName() const = 0;
+
+ protected:
+  // Requires that |target_function| is zero or the id of a function in
+  // |ir_context|.  If |target_function| is zero, returns all the functions in
+  // |ir_context|.  Otherwise, returns the function with id |target_function|.
+  // This allows fuzzer passes to restrict attention to a single function.
+  static std::vector<opt::Function*> GetTargetFunctions(
+      opt::IRContext* ir_context, uint32_t target_function);
 };
 
 }  // namespace reduce
diff --git a/source/reduce/reduction_pass.cpp b/source/reduce/reduction_pass.cpp
index 2cb986d..c6d1ebf 100644
--- a/source/reduce/reduction_pass.cpp
+++ b/source/reduce/reduction_pass.cpp
@@ -22,7 +22,7 @@
 namespace reduce {
 
 std::vector<uint32_t> ReductionPass::TryApplyReduction(
-    const std::vector<uint32_t>& binary) {
+    const std::vector<uint32_t>& binary, uint32_t target_function) {
   // We represent modules as binaries because (a) attempts at reduction need to
   // end up in binary form to be passed on to SPIR-V-consuming tools, and (b)
   // when we apply a reduction step we need to do it on a fresh version of the
@@ -34,7 +34,7 @@
   assert(context);
 
   std::vector<std::unique_ptr<ReductionOpportunity>> opportunities =
-      finder_->GetAvailableOpportunities(context.get());
+      finder_->GetAvailableOpportunities(context.get(), target_function);
 
   // There is no point in having a granularity larger than the number of
   // opportunities, so reduce the granularity in this case.
diff --git a/source/reduce/reduction_pass.h b/source/reduce/reduction_pass.h
index f2d937b..1836182 100644
--- a/source/reduce/reduction_pass.h
+++ b/source/reduce/reduction_pass.h
@@ -49,7 +49,12 @@
   // Returns an empty vector if there are no more chunks left to apply; in this
   // case, the index will be reset and the granularity lowered for the next
   // round.
-  std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary);
+  //
+  // If |target_function| is non-zero, only reduction opportunities that
+  // simplify the internals of the function with result id |target_function|
+  // will be applied.
+  std::vector<uint32_t> TryApplyReduction(const std::vector<uint32_t>& binary,
+                                          uint32_t target_function);
 
   // Notifies the reduction pass whether the binary returned from
   // TryApplyReduction is interesting, so that the next call to
diff --git a/source/reduce/reduction_util.cpp b/source/reduce/reduction_util.cpp
index 6f128dc..511f432 100644
--- a/source/reduce/reduction_util.cpp
+++ b/source/reduce/reduction_util.cpp
@@ -15,17 +15,73 @@
 #include "source/reduce/reduction_util.h"
 
 #include "source/opt/ir_context.h"
+#include "source/util/make_unique.h"
 
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 const uint32_t kTrueBranchOperandIndex = 1;
 const uint32_t kFalseBranchOperandIndex = 2;
 
-uint32_t FindOrCreateGlobalUndef(IRContext* context, uint32_t type_id) {
+uint32_t FindOrCreateGlobalVariable(opt::IRContext* context,
+                                    uint32_t pointer_type_id) {
+  for (auto& inst : context->module()->types_values()) {
+    if (inst.opcode() != SpvOpVariable) {
+      continue;
+    }
+    if (inst.type_id() == pointer_type_id) {
+      return inst.result_id();
+    }
+  }
+  const uint32_t variable_id = context->TakeNextId();
+  auto variable_inst = MakeUnique<opt::Instruction>(
+      context, SpvOpVariable, pointer_type_id, variable_id,
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_STORAGE_CLASS,
+            {static_cast<uint32_t>(context->get_type_mgr()
+                                       ->GetType(pointer_type_id)
+                                       ->AsPointer()
+                                       ->storage_class())}}}));
+  context->module()->AddGlobalValue(std::move(variable_inst));
+  return variable_id;
+}
+
+uint32_t FindOrCreateFunctionVariable(opt::IRContext* context,
+                                      opt::Function* function,
+                                      uint32_t pointer_type_id) {
+  // The pointer type of a function variable must have Function storage class.
+  assert(context->get_type_mgr()
+             ->GetType(pointer_type_id)
+             ->AsPointer()
+             ->storage_class() == SpvStorageClassFunction);
+
+  // Go through the instructions in the function's first block until we find a
+  // suitable variable, or go past all the variables.
+  opt::BasicBlock::iterator iter = function->begin()->begin();
+  for (;; ++iter) {
+    // We will either find a suitable variable, or find a non-variable
+    // instruction; we won't exhaust all instructions.
+    assert(iter != function->begin()->end());
+    if (iter->opcode() != SpvOpVariable) {
+      // If we see a non-variable, we have gone through all the variables.
+      break;
+    }
+    if (iter->type_id() == pointer_type_id) {
+      return iter->result_id();
+    }
+  }
+  // At this point, iter refers to the first non-function instruction of the
+  // function's entry block.
+  const uint32_t variable_id = context->TakeNextId();
+  auto variable_inst = MakeUnique<opt::Instruction>(
+      context, SpvOpVariable, pointer_type_id, variable_id,
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
+  iter->InsertBefore(std::move(variable_inst));
+  return variable_id;
+}
+
+uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id) {
   for (auto& inst : context->module()->types_values()) {
     if (inst.opcode() != SpvOpUndef) {
       continue;
@@ -34,11 +90,9 @@
       return inst.result_id();
     }
   }
-  // TODO(2182): this is adapted from MemPass::Type2Undef.  In due course it
-  // would be good to factor out this duplication.
   const uint32_t undef_id = context->TakeNextId();
-  std::unique_ptr<Instruction> undef_inst(
-      new Instruction(context, SpvOpUndef, type_id, undef_id, {}));
+  auto undef_inst = MakeUnique<opt::Instruction>(
+      context, SpvOpUndef, type_id, undef_id, opt::Instruction::OperandList());
   assert(undef_id == undef_inst->result_id());
   context->module()->AddGlobalValue(std::move(undef_inst));
   return undef_id;
@@ -46,8 +100,8 @@
 
 void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id,
                                         opt::BasicBlock* to_block) {
-  to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) {
-    Instruction::OperandList new_in_operands;
+  to_block->ForEachPhiInst([&from_id](opt::Instruction* phi_inst) {
+    opt::Instruction::OperandList new_in_operands;
     // Go through the OpPhi's input operands in (variable, parent) pairs.
     for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) {
       // Keep all pairs where the parent is not the block from which the edge
diff --git a/source/reduce/reduction_util.h b/source/reduce/reduction_util.h
index 7e7e153..bcdb77c 100644
--- a/source/reduce/reduction_util.h
+++ b/source/reduce/reduction_util.h
@@ -26,6 +26,16 @@
 extern const uint32_t kTrueBranchOperandIndex;
 extern const uint32_t kFalseBranchOperandIndex;
 
+// Returns a global OpVariable of type |pointer_type_id|, adding one if none
+// exist.
+uint32_t FindOrCreateGlobalVariable(opt::IRContext* context,
+                                    uint32_t pointer_type_id);
+
+// Returns an OpVariable of type |pointer_type_id| declared in |function|,
+// adding one if none exist.
+uint32_t FindOrCreateFunctionVariable(opt::IRContext* context, opt::Function*,
+                                      uint32_t pointer_type_id);
+
 // Returns an OpUndef id from the global value list that is of the given type,
 // adding one if it does not exist.
 uint32_t FindOrCreateGlobalUndef(opt::IRContext* context, uint32_t type_id);
diff --git a/source/reduce/remove_block_reduction_opportunity.cpp b/source/reduce/remove_block_reduction_opportunity.cpp
index 3ad7f72..aa48105 100644
--- a/source/reduce/remove_block_reduction_opportunity.cpp
+++ b/source/reduce/remove_block_reduction_opportunity.cpp
@@ -19,11 +19,8 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::Function;
-
 RemoveBlockReductionOpportunity::RemoveBlockReductionOpportunity(
-    Function* function, BasicBlock* block)
+    opt::Function* function, opt::BasicBlock* block)
     : function_(function), block_(block) {
   // precondition:
   assert(block_->begin() != block_->end() &&
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.cpp b/source/reduce/remove_block_reduction_opportunity_finder.cpp
index a3f873f..27a4570 100644
--- a/source/reduce/remove_block_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_block_reduction_opportunity_finder.cpp
@@ -19,25 +19,21 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::Function;
-using opt::IRContext;
-using opt::Instruction;
-
 std::string RemoveBlockReductionOpportunityFinder::GetName() const {
   return "RemoveBlockReductionOpportunityFinder";
 }
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveBlockReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
-  // Consider every block in every function.
-  for (auto& function : *context->module()) {
-    for (auto bi = function.begin(); bi != function.end(); ++bi) {
-      if (IsBlockValidOpportunity(context, function, bi)) {
-        result.push_back(spvtools::MakeUnique<RemoveBlockReductionOpportunity>(
-            &function, &*bi));
+  // Consider every block in every relevant function.
+  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));
       }
     }
   }
@@ -45,21 +41,22 @@
 }
 
 bool RemoveBlockReductionOpportunityFinder::IsBlockValidOpportunity(
-    IRContext* context, Function& function, Function::iterator& bi) {
-  assert(bi != function.end() && "Block iterator was out of bounds");
+    opt::IRContext* context, opt::Function* function,
+    opt::Function::iterator* bi) {
+  assert(*bi != function->end() && "Block iterator was out of bounds");
 
   // Don't remove first block; we don't want to end up with no blocks.
-  if (bi == function.begin()) {
+  if (*bi == function->begin()) {
     return false;
   }
 
   // Don't remove blocks with references.
-  if (context->get_def_use_mgr()->NumUsers(bi->id()) > 0) {
+  if (context->get_def_use_mgr()->NumUsers((*bi)->id()) > 0) {
     return false;
   }
 
   // Don't remove blocks whose instructions have outside references.
-  if (!BlockInstructionsHaveNoOutsideReferences(context, bi)) {
+  if (!BlockInstructionsHaveNoOutsideReferences(context, *bi)) {
     return false;
   }
 
@@ -67,19 +64,19 @@
 }
 
 bool RemoveBlockReductionOpportunityFinder::
-    BlockInstructionsHaveNoOutsideReferences(IRContext* context,
-                                             const Function::iterator& bi) {
+    BlockInstructionsHaveNoOutsideReferences(
+        opt::IRContext* context, const opt::Function::iterator& bi) {
   // Get all instructions in block.
   std::unordered_set<uint32_t> instructions_in_block;
-  for (const Instruction& instruction : *bi) {
+  for (const opt::Instruction& instruction : *bi) {
     instructions_in_block.insert(instruction.unique_id());
   }
 
   // For each instruction...
-  for (const Instruction& instruction : *bi) {
+  for (const opt::Instruction& instruction : *bi) {
     // For each use of the instruction...
     bool no_uses_outside_block = context->get_def_use_mgr()->WhileEachUser(
-        &instruction, [&instructions_in_block](Instruction* user) -> bool {
+        &instruction, [&instructions_in_block](opt::Instruction* user) -> bool {
           // If the use is in this block, continue (return true). Otherwise, we
           // found an outside use; return false (and stop).
           return instructions_in_block.find(user->unique_id()) !=
diff --git a/source/reduce/remove_block_reduction_opportunity_finder.h b/source/reduce/remove_block_reduction_opportunity_finder.h
index 83cd04b..d347bf9 100644
--- a/source/reduce/remove_block_reduction_opportunity_finder.h
+++ b/source/reduce/remove_block_reduction_opportunity_finder.h
@@ -34,14 +34,14 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   // Returns true if the block |bi| in function |function| is a valid
   // opportunity according to various restrictions.
   static bool IsBlockValidOpportunity(opt::IRContext* context,
-                                      opt::Function& function,
-                                      opt::Function::iterator& bi);
+                                      opt::Function* function,
+                                      opt::Function::iterator* bi);
 
   // Returns true if the instructions (definitions) in block |bi| have no
   // references, except for references from inside the block itself.
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.cpp b/source/reduce/remove_function_reduction_opportunity_finder.cpp
index 1edb973..1d8d972 100644
--- a/source/reduce/remove_function_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_function_reduction_opportunity_finder.cpp
@@ -21,7 +21,14 @@
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
+  if (target_function) {
+    // If we are targeting a specific function then we are only interested in
+    // opportunities that simplify the internals of that function; removing
+    // whole functions does not fit the bill.
+    return {};
+  }
+
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
   // Consider each function.
   for (auto& function : *context->module()) {
diff --git a/source/reduce/remove_function_reduction_opportunity_finder.h b/source/reduce/remove_function_reduction_opportunity_finder.h
index 7952a22..6fcfb77 100644
--- a/source/reduce/remove_function_reduction_opportunity_finder.h
+++ b/source/reduce/remove_function_reduction_opportunity_finder.h
@@ -31,7 +31,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.cpp b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
index 45821e2..74df1b8 100644
--- a/source/reduce/remove_selection_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.cpp
@@ -19,10 +19,6 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::IRContext;
-using opt::Instruction;
-
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 const uint32_t kContinueNodeIndex = 1;
@@ -34,11 +30,11 @@
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveSelectionReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   // Get all loop merge and continue blocks so we can check for these later.
   std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops;
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       if (auto merge_instruction = block.GetMergeInst()) {
         if (merge_instruction->opcode() == SpvOpLoopMerge) {
           uint32_t merge_block_id =
@@ -73,8 +69,8 @@
 }
 
 bool RemoveSelectionReductionOpportunityFinder::CanOpSelectionMergeBeRemoved(
-    IRContext* context, const BasicBlock& header_block,
-    Instruction* merge_instruction,
+    opt::IRContext* context, const opt::BasicBlock& header_block,
+    opt::Instruction* merge_instruction,
     std::unordered_set<uint32_t> merge_and_continue_blocks_from_loops) {
   assert(header_block.GetMergeInst() == merge_instruction &&
          "CanOpSelectionMergeBeRemoved(...): header block and merge "
@@ -122,7 +118,7 @@
         merge_instruction->GetSingleWordOperand(kMergeNodeIndex);
     for (uint32_t predecessor_block_id :
          context->cfg()->preds(merge_block_id)) {
-      const BasicBlock* predecessor_block =
+      const opt::BasicBlock* predecessor_block =
           context->cfg()->block(predecessor_block_id);
       assert(predecessor_block);
       bool found_divergent_successor = false;
diff --git a/source/reduce/remove_selection_reduction_opportunity_finder.h b/source/reduce/remove_selection_reduction_opportunity_finder.h
index 848122b..1a17493 100644
--- a/source/reduce/remove_selection_reduction_opportunity_finder.h
+++ b/source/reduce/remove_selection_reduction_opportunity_finder.h
@@ -33,7 +33,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
   // Returns true if the OpSelectionMerge instruction |merge_instruction| in
   // block |header_block| can be removed.
diff --git a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp
index 91ec542..d7bb3a8 100644
--- a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.cpp
@@ -28,61 +28,72 @@
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveUnusedInstructionReductionOpportunityFinder::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
-  for (auto& inst : context->module()->debugs1()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
+  if (!target_function) {
+    // We are not restricting reduction to a specific function, so we consider
+    // unused instructions defined outside functions.
+
+    for (auto& inst : context->module()->debugs1()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
     }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+
+    for (auto& inst : context->module()->debugs2()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+    }
+
+    for (auto& inst : context->module()->debugs3()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+    }
+
+    for (auto& inst : context->module()->ext_inst_debuginfo()) {
+      if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+    }
+
+    for (auto& inst : context->module()->types_values()) {
+      if (!remove_constants_and_undefs_ &&
+          spvOpcodeIsConstantOrUndef(inst.opcode())) {
+        continue;
+      }
+      if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context,
+                                                                   inst)) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+    }
+
+    for (auto& inst : context->module()->annotations()) {
+      if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
+        continue;
+      }
+      if (!IsIndependentlyRemovableDecoration(inst)) {
+        continue;
+      }
+      result.push_back(
+          MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
+    }
   }
 
-  for (auto& inst : context->module()->debugs2()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
-    }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
-
-  for (auto& inst : context->module()->debugs3()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
-    }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
-
-  for (auto& inst : context->module()->ext_inst_debuginfo()) {
-    if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
-      continue;
-    }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
-
-  for (auto& inst : context->module()->types_values()) {
-    if (!remove_constants_and_undefs_ &&
-        spvOpcodeIsConstantOrUndef(inst.opcode())) {
-      continue;
-    }
-    if (!OnlyReferencedByIntimateDecorationOrEntryPointInterface(context,
-                                                                 inst)) {
-      continue;
-    }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
-
-  for (auto& inst : context->module()->annotations()) {
-    if (context->get_def_use_mgr()->NumUsers(&inst) > 0) {
-      continue;
-    }
-    if (!IsIndependentlyRemovableDecoration(inst)) {
-      continue;
-    }
-    result.push_back(MakeUnique<RemoveInstructionReductionOpportunity>(&inst));
-  }
-
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       for (auto& inst : block) {
         if (context->get_def_use_mgr()->NumUses(&inst) > 0) {
           continue;
diff --git a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h
index cbf6a5b..0323640 100644
--- a/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h
+++ b/source/reduce/remove_unused_instruction_reduction_opportunity_finder.h
@@ -38,7 +38,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   // Returns true if and only if the only uses of |inst| are by decorations that
diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
index 39ce47f..e72be62 100644
--- a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
+++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.cpp
@@ -24,7 +24,14 @@
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities(
-    opt::IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
+  if (target_function) {
+    // Removing an unused struct member is a global change, as struct types are
+    // global.  We thus do not consider such opportunities if we are targeting
+    // a specific function.
+    return {};
+  }
+
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // We track those struct members that are never accessed.  We do this by
diff --git a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h
index 13f4017..98f9c01 100644
--- a/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h
+++ b/source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h
@@ -32,7 +32,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
   // A helper method to update |unused_members_to_structs| by removing from it
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
index 17a5c7e..d867c3a 100644
--- a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp
@@ -20,20 +20,17 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-using opt::Instruction;
-
 std::vector<std::unique_ptr<ReductionOpportunity>>
 SimpleConditionalBranchToBranchOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   // Consider every function.
-  for (auto& function : *context->module()) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
     // Consider every block in the function.
-    for (auto& block : function) {
+    for (auto& block : *function) {
       // The terminator must be SpvOpBranchConditional.
-      Instruction* terminator = block.terminator();
+      opt::Instruction* terminator = block.terminator();
       if (terminator->opcode() != SpvOpBranchConditional) {
         continue;
       }
diff --git a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
index 10b9dce..8869908 100644
--- a/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
+++ b/source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h
@@ -26,7 +26,7 @@
     : public ReductionOpportunityFinder {
  public:
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const override;
+      opt::IRContext* context, uint32_t target_function) const override;
 
   std::string GetName() const override;
 };
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 8968b96..ca17f9e 100644
--- a/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
+++ b/source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp
@@ -19,11 +19,9 @@
 namespace spvtools {
 namespace reduce {
 
-using namespace opt;
-
 SimpleConditionalBranchToBranchReductionOpportunity::
     SimpleConditionalBranchToBranchReductionOpportunity(
-        Instruction* conditional_branch_instruction)
+        opt::Instruction* conditional_branch_instruction)
     : conditional_branch_instruction_(conditional_branch_instruction) {}
 
 bool SimpleConditionalBranchToBranchReductionOpportunity::PreconditionHolds() {
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
index 88ea38e..0c00443 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp
@@ -21,11 +21,6 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::BasicBlock;
-using opt::IRContext;
-using opt::Instruction;
-using opt::Operand;
-
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 }  // namespace
@@ -58,14 +53,16 @@
 
   // We have made control flow changes that do not preserve the analyses that
   // were performed.
-  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+  context_->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 
   // (4) By changing CFG edges we may have created scenarios where ids are used
   // without being dominated; we fix instances of this.
   FixNonDominatedIdUses();
 
   // Invalidate the analyses we just used.
-  context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone);
+  context_->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
 }
 
 void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock(
@@ -168,13 +165,14 @@
 }
 
 void StructuredLoopToSelectionReductionOpportunity::
-    AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) {
-  to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) {
+    AdaptPhiInstructionsForAddedEdge(uint32_t from_id,
+                                     opt::BasicBlock* to_block) {
+  to_block->ForEachPhiInst([this, &from_id](opt::Instruction* phi_inst) {
     // Add to the phi operand an (undef, from_id) pair to reflect the added
     // edge.
     auto undef_id = FindOrCreateGlobalUndef(context_, phi_inst->type_id());
-    phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
-    phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id}));
+    phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {undef_id}));
+    phi_inst->AddOperand(opt::Operand(SPV_OPERAND_TYPE_ID, {from_id}));
   });
 }
 
@@ -227,7 +225,7 @@
         continue;
       }
       context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def](
-                                                        Instruction* use,
+                                                        opt::Instruction* use,
                                                         uint32_t index) {
         // Ignore uses outside of blocks, such as in OpDecorate.
         if (context_->get_instr_block(use) == nullptr) {
@@ -245,17 +243,20 @@
               case SpvStorageClassFunction:
                 use->SetOperand(
                     index, {FindOrCreateFunctionVariable(
+                               context_, enclosing_function_,
                                context_->get_type_mgr()->GetId(pointer_type))});
                 break;
               default:
                 // TODO(2183) Need to think carefully about whether it makes
-                // sense to add new variables for all storage classes; it's fine
-                // for Private but might not be OK for input/output storage
-                // classes for example.
+                //  sense to add new variables for all storage classes; it's
+                //  fine for Private but might not be OK for input/output
+                //  storage classes for example.
                 use->SetOperand(
                     index, {FindOrCreateGlobalVariable(
+                               context_,
                                context_->get_type_mgr()->GetId(pointer_type))});
                 break;
+                break;
             }
           } else {
             use->SetOperand(index,
@@ -268,9 +269,10 @@
 }
 
 bool StructuredLoopToSelectionReductionOpportunity::
-    DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use,
+    DefinitionSufficientlyDominatesUse(opt::Instruction* def,
+                                       opt::Instruction* use,
                                        uint32_t use_index,
-                                       BasicBlock& def_block) {
+                                       opt::BasicBlock& def_block) {
   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.
@@ -282,62 +284,5 @@
       ->Dominates(def, use);
 }
 
-uint32_t
-StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable(
-    uint32_t pointer_type_id) {
-  for (auto& inst : context_->module()->types_values()) {
-    if (inst.opcode() != SpvOpVariable) {
-      continue;
-    }
-    if (inst.type_id() == pointer_type_id) {
-      return inst.result_id();
-    }
-  }
-  const uint32_t variable_id = context_->TakeNextId();
-  std::unique_ptr<Instruction> variable_inst(
-      new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id,
-                      {{SPV_OPERAND_TYPE_STORAGE_CLASS,
-                        {(uint32_t)context_->get_type_mgr()
-                             ->GetType(pointer_type_id)
-                             ->AsPointer()
-                             ->storage_class()}}}));
-  context_->module()->AddGlobalValue(std::move(variable_inst));
-  return variable_id;
-}
-
-uint32_t
-StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable(
-    uint32_t pointer_type_id) {
-  // The pointer type of a function variable must have Function storage class.
-  assert(context_->get_type_mgr()
-             ->GetType(pointer_type_id)
-             ->AsPointer()
-             ->storage_class() == SpvStorageClassFunction);
-
-  // Go through the instructions in the function's first block until we find a
-  // suitable variable, or go past all the variables.
-  BasicBlock::iterator iter = enclosing_function_->begin()->begin();
-  for (;; ++iter) {
-    // We will either find a suitable variable, or find a non-variable
-    // instruction; we won't exhaust all instructions.
-    assert(iter != enclosing_function_->begin()->end());
-    if (iter->opcode() != SpvOpVariable) {
-      // If we see a non-variable, we have gone through all the variables.
-      break;
-    }
-    if (iter->type_id() == pointer_type_id) {
-      return iter->result_id();
-    }
-  }
-  // At this point, iter refers to the first non-function instruction of the
-  // function's entry block.
-  const uint32_t variable_id = context_->TakeNextId();
-  std::unique_ptr<Instruction> variable_inst(new Instruction(
-      context_, SpvOpVariable, pointer_type_id, variable_id,
-      {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
-  iter->InsertBefore(std::move(variable_inst));
-  return variable_id;
-}
-
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
index 564811f..4c57619 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h
@@ -72,8 +72,8 @@
   void ChangeLoopToSelection();
 
   // Fixes any scenarios where, due to CFG changes, ids have uses not dominated
-  // by their definitions, by changing such uses to uses of OpUndef or of dummy
-  // variables.
+  // by their definitions, by changing such uses to uses of OpUndef or of
+  // placeholder variables.
   void FixNonDominatedIdUses();
 
   // Returns true if and only if at least one of the following holds:
@@ -86,20 +86,6 @@
                                           uint32_t use_index,
                                           opt::BasicBlock& def_block);
 
-  // Checks whether the global value list has an OpVariable of the given pointer
-  // type, adding one if not, and returns the id of such an OpVariable.
-  //
-  // TODO(2184): This will likely be used by other reduction passes, so should
-  // be factored out in due course.
-  uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id);
-
-  // Checks whether the enclosing function has an OpVariable of the given
-  // pointer type, adding one if not, and returns the id of such an OpVariable.
-  //
-  // TODO(2184): This will likely be used by other reduction passes, so should
-  // be factored out in due course.
-  uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id);
-
   opt::IRContext* context_;
   opt::BasicBlock* loop_construct_header_;
   opt::Function* enclosing_function_;
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 085b267..fdf3ab0 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp
@@ -19,8 +19,6 @@
 namespace spvtools {
 namespace reduce {
 
-using opt::IRContext;
-
 namespace {
 const uint32_t kMergeNodeIndex = 0;
 const uint32_t kContinueNodeIndex = 1;
@@ -28,12 +26,12 @@
 
 std::vector<std::unique_ptr<ReductionOpportunity>>
 StructuredLoopToSelectionReductionOpportunityFinder::GetAvailableOpportunities(
-    IRContext* context) const {
+    opt::IRContext* context, uint32_t target_function) const {
   std::vector<std::unique_ptr<ReductionOpportunity>> result;
 
   std::set<uint32_t> merge_block_ids;
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       auto merge_block_id = block.MergeBlockIdIfAny();
       if (merge_block_id) {
         merge_block_ids.insert(merge_block_id);
@@ -42,8 +40,8 @@
   }
 
   // Consider each loop construct header in the module.
-  for (auto& function : *context->module()) {
-    for (auto& block : function) {
+  for (auto* function : GetTargetFunctions(context, target_function)) {
+    for (auto& block : *function) {
       auto loop_merge_inst = block.GetLoopMergeInst();
       if (!loop_merge_inst) {
         // This is not a loop construct header.
@@ -71,8 +69,8 @@
       // so we cautiously do not consider applying a transformation.
       auto merge_block_id =
           loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex);
-      if (!context->GetDominatorAnalysis(&function)->Dominates(
-              block.id(), merge_block_id)) {
+      if (!context->GetDominatorAnalysis(function)->Dominates(block.id(),
+                                                              merge_block_id)) {
         continue;
       }
 
@@ -80,7 +78,7 @@
       // construct header.  If not (e.g. because the loop contains OpReturn,
       // OpKill or OpUnreachable), we cautiously do not consider applying
       // a transformation.
-      if (!context->GetPostDominatorAnalysis(&function)->Dominates(
+      if (!context->GetPostDominatorAnalysis(function)->Dominates(
               merge_block_id, block.id())) {
         continue;
       }
@@ -89,7 +87,7 @@
       // opportunity to do so.
       result.push_back(
           MakeUnique<StructuredLoopToSelectionReductionOpportunity>(
-              context, &block, &function));
+              context, &block, function));
     }
   }
   return result;
diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
index d63d434..6166af3 100644
--- a/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
+++ b/source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h
@@ -46,7 +46,7 @@
   std::string GetName() const final;
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      opt::IRContext* context) const final;
+      opt::IRContext* context, uint32_t target_function) const final;
 
  private:
 };
diff --git a/source/spirv_fuzzer_options.cpp b/source/spirv_fuzzer_options.cpp
index 64fefbc..3f62e0e 100644
--- a/source/spirv_fuzzer_options.cpp
+++ b/source/spirv_fuzzer_options.cpp
@@ -25,7 +25,8 @@
       replay_range(0),
       replay_validation_enabled(false),
       shrinker_step_limit(kDefaultStepLimit),
-      fuzzer_pass_validation_enabled(false) {}
+      fuzzer_pass_validation_enabled(false),
+      all_passes_enabled(false) {}
 
 SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() {
   return new spv_fuzzer_options_t();
@@ -60,3 +61,8 @@
     spv_fuzzer_options options) {
   options->fuzzer_pass_validation_enabled = true;
 }
+
+SPIRV_TOOLS_EXPORT void spvFuzzerOptionsEnableAllPasses(
+    spv_fuzzer_options options) {
+  options->all_passes_enabled = true;
+}
diff --git a/source/spirv_fuzzer_options.h b/source/spirv_fuzzer_options.h
index 0db16a3..bb8d910 100644
--- a/source/spirv_fuzzer_options.h
+++ b/source/spirv_fuzzer_options.h
@@ -40,6 +40,9 @@
 
   // See spvFuzzerOptionsValidateAfterEveryPass.
   bool fuzzer_pass_validation_enabled;
+
+  // See spvFuzzerOptionsEnableAllPasses.
+  bool all_passes_enabled;
 };
 
 #endif  // SOURCE_SPIRV_FUZZER_OPTIONS_H_
diff --git a/source/spirv_reducer_options.cpp b/source/spirv_reducer_options.cpp
index e807875..9086433 100644
--- a/source/spirv_reducer_options.cpp
+++ b/source/spirv_reducer_options.cpp
@@ -23,7 +23,9 @@
 }  // namespace
 
 spv_reducer_options_t::spv_reducer_options_t()
-    : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {}
+    : step_limit(kDefaultStepLimit),
+      fail_on_validation_error(false),
+      target_function(0) {}
 
 SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() {
   return new spv_reducer_options_t();
@@ -42,3 +44,8 @@
     spv_reducer_options options, bool fail_on_validation_error) {
   options->fail_on_validation_error = fail_on_validation_error;
 }
+
+SPIRV_TOOLS_EXPORT void spvReducerOptionsSetTargetFunction(
+    spv_reducer_options options, uint32_t target_function) {
+  options->target_function = target_function;
+}
diff --git a/source/spirv_reducer_options.h b/source/spirv_reducer_options.h
index 1a431cc..911747d 100644
--- a/source/spirv_reducer_options.h
+++ b/source/spirv_reducer_options.h
@@ -30,6 +30,9 @@
 
   // See spvReducerOptionsSetFailOnValidationError.
   bool fail_on_validation_error;
+
+  // See spvReducerOptionsSetTargetFunction.
+  uint32_t target_function;
 };
 
 #endif  // SOURCE_SPIRV_REDUCER_OPTIONS_H_
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index f964b9b..d6e992b 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -284,9 +284,10 @@
         const auto execution_model = inst->GetOperandAs<SpvExecutionModel>(0);
         const char* str = reinterpret_cast<const char*>(
             inst->words().data() + inst->operand(2).offset);
+        const std::string desc_name(str);
 
         ValidationState_t::EntryPointDescription desc;
-        desc.name = str;
+        desc.name = desc_name;
 
         std::vector<uint32_t> interfaces;
         for (size_t j = 3; j < inst->operands().size(); ++j)
@@ -303,7 +304,7 @@
                 check_inst->words().data() + inst->operand(2).offset);
             const std::string check_name(check_str);
 
-            if (desc.name == check_name &&
+            if (desc_name == check_name &&
                 execution_model == check_execution_model) {
               return vstate->diag(SPV_ERROR_INVALID_DATA, inst)
                      << "2 Entry points cannot share the same name and "
diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp
index df7973f..3f1f561 100644
--- a/source/val/validate_atomics.cpp
+++ b/source/val/validate_atomics.cpp
@@ -1,4 +1,6 @@
 // Copyright (c) 2017 Google Inc.
+// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
+// reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -151,7 +153,9 @@
         }
 
         if (spvIsVulkanEnv(_.context()->target_env) &&
-            _.GetBitWidth(result_type) != 32) {
+            (_.GetBitWidth(result_type) != 32 &&
+             (_.GetBitWidth(result_type) != 64 ||
+              !_.HasCapability(SpvCapabilityInt64ImageEXT)))) {
           switch (opcode) {
             case SpvOpAtomicSMin:
             case SpvOpAtomicUMin:
diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp
index d86c91e..1d85f88 100644
--- a/source/val/validate_builtins.cpp
+++ b/source/val/validate_builtins.cpp
@@ -1,4 +1,6 @@
 // Copyright (c) 2018 Google LLC.
+// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
+// reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,8 +16,6 @@
 
 // Validates correctness of built-in variables.
 
-#include "source/val/validate.h"
-
 #include <functional>
 #include <list>
 #include <map>
@@ -31,6 +31,7 @@
 #include "source/spirv_target_env.h"
 #include "source/util/bitutils.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -205,6 +206,14 @@
       const Decoration& decoration, const Instruction& inst);
   spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration,
                                                  const Instruction& inst);
+  spv_result_t ValidateBaseInstanceOrVertexAtDefinition(
+      const Decoration& decoration, const Instruction& inst);
+  spv_result_t ValidateDrawIndexAtDefinition(const Decoration& decoration,
+                                             const Instruction& inst);
+  spv_result_t ValidateViewIndexAtDefinition(const Decoration& decoration,
+                                             const Instruction& inst);
+  spv_result_t ValidateDeviceIndexAtDefinition(const Decoration& decoration,
+                                               const Instruction& inst);
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
   spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition(
       const Decoration& decoration, const Instruction& inst);
@@ -222,6 +231,12 @@
   spv_result_t ValidateComputeI32InputAtDefinition(const Decoration& decoration,
                                                    const Instruction& inst);
 
+  spv_result_t ValidatePrimitiveShadingRateAtDefinition(
+      const Decoration& decoration, const Instruction& inst);
+
+  spv_result_t ValidateShadingRateAtDefinition(const Decoration& decoration,
+                                               const Instruction& inst);
+
   // The following section contains functions which are called when id defined
   // by |referenced_inst| is
   // 1. referenced by |referenced_from_inst|
@@ -339,6 +354,26 @@
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
 
+  spv_result_t ValidateBaseInstanceOrVertexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
+  spv_result_t ValidateDrawIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
+  spv_result_t ValidateViewIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
+  spv_result_t ValidateDeviceIndexAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
   // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId.
   spv_result_t ValidateComputeShaderI32Vec3InputAtReference(
       const Decoration& decoration, const Instruction& built_in_inst,
@@ -355,6 +390,16 @@
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
 
+  spv_result_t ValidatePrimitiveShadingRateAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
+  spv_result_t ValidateShadingRateAtReference(
+      const Decoration& decoration, const Instruction& built_in_inst,
+      const Instruction& referenced_inst,
+      const Instruction& referenced_from_inst);
+
   // Validates that |built_in_inst| is not (even indirectly) referenced from
   // within a function which can be called with |execution_model|.
   //
@@ -365,7 +410,7 @@
   // |referenced_from_inst| - instruction which references id defined by
   //                          |referenced_inst| from within a function.
   spv_result_t ValidateNotCalledWithExecutionModel(
-      const char* comment, SpvExecutionModel execution_model,
+      std::string comment, SpvExecutionModel execution_model,
       const Decoration& decoration, const Instruction& built_in_inst,
       const Instruction& referenced_inst,
       const Instruction& referenced_from_inst);
@@ -864,7 +909,7 @@
 }
 
 spv_result_t BuiltInsValidator::ValidateNotCalledWithExecutionModel(
-    const char* comment, SpvExecutionModel execution_model,
+    std::string comment, SpvExecutionModel execution_model,
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
@@ -903,6 +948,7 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
   if (spvIsVulkanEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
@@ -911,7 +957,7 @@
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
              << "Vulkan spec allows BuiltIn "
              << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                              decoration.params()[0])
+                                              operand)
              << " to be only used for variables with Input or Output storage "
                 "class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -949,7 +995,12 @@
                   decoration, built_in_inst, /* Any number of components */ 0,
                   [this, &decoration, &referenced_from_inst](
                       const std::string& message) -> spv_result_t {
+                    uint32_t vuid =
+                        (decoration.params()[0] == SpvBuiltInClipDistance)
+                            ? 4191
+                            : 4200;
                     return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                           << _.VkErrorID(vuid)
                            << "According to the Vulkan spec BuiltIn "
                            << _.grammar().lookupOperandName(
                                   SPV_OPERAND_TYPE_BUILT_IN,
@@ -971,8 +1022,13 @@
                     decoration, built_in_inst, /* Any number of components */ 0,
                     [this, &decoration, &referenced_from_inst](
                         const std::string& message) -> spv_result_t {
+                      uint32_t vuid =
+                          (decoration.params()[0] == SpvBuiltInClipDistance)
+                              ? 4191
+                              : 4200;
                       return _.diag(SPV_ERROR_INVALID_DATA,
                                     &referenced_from_inst)
+                             << _.VkErrorID(vuid)
                              << "According to the Vulkan spec BuiltIn "
                              << _.grammar().lookupOperandName(
                                     SPV_OPERAND_TYPE_BUILT_IN,
@@ -987,8 +1043,13 @@
                     decoration, built_in_inst, /* Any number of components */ 0,
                     [this, &decoration, &referenced_from_inst](
                         const std::string& message) -> spv_result_t {
+                      uint32_t vuid =
+                          (decoration.params()[0] == SpvBuiltInClipDistance)
+                              ? 4191
+                              : 4200;
                       return _.diag(SPV_ERROR_INVALID_DATA,
                                     &referenced_from_inst)
+                             << _.VkErrorID(vuid)
                              << "According to the Vulkan spec BuiltIn "
                              << _.grammar().lookupOperandName(
                                     SPV_OPERAND_TYPE_BUILT_IN,
@@ -1003,10 +1064,12 @@
         }
 
         default: {
+          uint32_t vuid =
+              (decoration.params()[0] == SpvBuiltInClipDistance) ? 4187 : 4196;
           return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-                 << "Vulkan spec allows BuiltIn "
+                 << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
                  << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                                  decoration.params()[0])
+                                                  operand)
                  << " to be used only with Fragment, Vertex, "
                     "TessellationControl, TessellationEvaluation or Geometry "
                     "execution models. "
@@ -1035,7 +1098,7 @@
             decoration, inst, 4,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the "
+                     << _.VkErrorID(4212) << "According to the "
                      << spvLogStringForEnv(_.context()->target_env)
                      << " spec BuiltIn FragCoord "
                         "variable needs to be a 4-component 32-bit float "
@@ -1059,7 +1122,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << spvLogStringForEnv(_.context()->target_env)
+             << _.VkErrorID(4211) << spvLogStringForEnv(_.context()->target_env)
              << " spec allows BuiltIn FragCoord to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1070,6 +1133,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4210)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn FragCoord to be used only with "
                   "Fragment execution model. "
@@ -1096,7 +1160,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the "
+                     << _.VkErrorID(4215) << "According to the "
                      << spvLogStringForEnv(_.context()->target_env)
                      << " spec BuiltIn FragDepth "
                         "variable needs to be a 32-bit float scalar. "
@@ -1119,7 +1183,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassOutput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << spvLogStringForEnv(_.context()->target_env)
+             << _.VkErrorID(4214) << spvLogStringForEnv(_.context()->target_env)
              << " spec allows BuiltIn FragDepth to be only used for "
                 "variables with Output storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1130,6 +1194,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4213)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn FragDepth to be used only with "
                   "Fragment execution model. "
@@ -1144,6 +1209,7 @@
       const auto* modes = _.GetExecutionModes(entry_point);
       if (!modes || !modes->count(SpvExecutionModeDepthReplacing)) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4216)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec requires DepthReplacing execution mode to be "
                   "declared when using BuiltIn FragDepth. "
@@ -1170,7 +1236,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the "
+                     << _.VkErrorID(4231) << "According to the "
                      << spvLogStringForEnv(_.context()->target_env)
                      << " spec BuiltIn FrontFacing "
                         "variable needs to be a bool scalar. "
@@ -1193,7 +1259,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << spvLogStringForEnv(_.context()->target_env)
+             << _.VkErrorID(4230) << spvLogStringForEnv(_.context()->target_env)
              << " spec allows BuiltIn FrontFacing to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1204,6 +1270,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4229)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn FrontFacing to be used only with "
                   "Fragment execution model. "
@@ -1230,6 +1297,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4241)
                      << "According to the Vulkan spec BuiltIn HelperInvocation "
                         "variable needs to be a bool scalar. "
                      << message;
@@ -1251,6 +1319,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4240)
              << "Vulkan spec allows BuiltIn HelperInvocation to be only used "
                 "for variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1261,6 +1330,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4239)
                << "Vulkan spec allows BuiltIn HelperInvocation to be used only "
                   "with Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1287,6 +1357,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4259)
                      << "According to the Vulkan spec BuiltIn InvocationId "
                         "variable needs to be a 32-bit int scalar. "
                      << message;
@@ -1308,6 +1379,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4258)
              << "Vulkan spec allows BuiltIn InvocationId to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1319,6 +1391,7 @@
       if (execution_model != SpvExecutionModelTessellationControl &&
           execution_model != SpvExecutionModelGeometry) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4257)
                << "Vulkan spec allows BuiltIn InvocationId to be used only "
                   "with TessellationControl or Geometry execution models. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1344,7 +1417,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the "
+                     << _.VkErrorID(4265) << "According to the "
                      << spvLogStringForEnv(_.context()->target_env)
                      << " spec BuiltIn InstanceIndex "
                         "variable needs to be a 32-bit int scalar. "
@@ -1367,7 +1440,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << spvLogStringForEnv(_.context()->target_env)
+             << _.VkErrorID(4264) << spvLogStringForEnv(_.context()->target_env)
              << " spec allows BuiltIn InstanceIndex to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1378,6 +1451,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelVertex) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4263)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn InstanceIndex to be used only "
                   "with Vertex execution model. "
@@ -1404,6 +1478,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4310)
                      << "According to the Vulkan spec BuiltIn PatchVertices "
                         "variable needs to be a 32-bit int scalar. "
                      << message;
@@ -1425,6 +1500,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4309)
              << "Vulkan spec allows BuiltIn PatchVertices to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1436,6 +1512,7 @@
       if (execution_model != SpvExecutionModelTessellationControl &&
           execution_model != SpvExecutionModelTessellationEvaluation) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4308)
                << "Vulkan spec allows BuiltIn PatchVertices to be used only "
                   "with TessellationControl or TessellationEvaluation "
                   "execution models. "
@@ -1462,6 +1539,7 @@
             decoration, inst, 2,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4313)
                      << "According to the Vulkan spec BuiltIn PointCoord "
                         "variable needs to be a 2-component 32-bit float "
                         "vector. "
@@ -1484,6 +1562,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4312)
              << "Vulkan spec allows BuiltIn PointCoord to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1494,6 +1573,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4311)
                << "Vulkan spec allows BuiltIn PointCoord to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1528,6 +1608,7 @@
         storage_class != SpvStorageClassInput &&
         storage_class != SpvStorageClassOutput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4316)
              << "Vulkan spec allows BuiltIn PointSize to be only used for "
                 "variables with Input or Output storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1539,8 +1620,11 @@
       assert(function_id_ == 0);
       id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
           &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this,
-          "Vulkan spec doesn't allow BuiltIn PointSize to be used for "
-          "variables with Input storage class if execution model is Vertex.",
+          std::string(
+              _.VkErrorID(4315) +
+              "Vulkan spec doesn't allow BuiltIn PointSize to be used for "
+              "variables with Input storage class if execution model is "
+              "Vertex."),
           SpvExecutionModelVertex, decoration, built_in_inst,
           referenced_from_inst, std::placeholders::_1));
     }
@@ -1553,6 +1637,7 @@
                   [this, &referenced_from_inst](
                       const std::string& message) -> spv_result_t {
                     return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                           << _.VkErrorID(4317)
                            << "According to the Vulkan spec BuiltIn PointSize "
                               "variable needs to be a 32-bit float scalar. "
                            << message;
@@ -1576,6 +1661,7 @@
                         const std::string& message) -> spv_result_t {
                       return _.diag(SPV_ERROR_INVALID_DATA,
                                     &referenced_from_inst)
+                             << _.VkErrorID(4317)
                              << "According to the Vulkan spec BuiltIn "
                                 "PointSize variable needs to be a 32-bit "
                                 "float scalar. "
@@ -1590,6 +1676,7 @@
                         const std::string& message) -> spv_result_t {
                       return _.diag(SPV_ERROR_INVALID_DATA,
                                     &referenced_from_inst)
+                             << _.VkErrorID(4317)
                              << "According to the Vulkan spec BuiltIn "
                                 "PointSize variable needs to be a 32-bit "
                                 "float scalar. "
@@ -1603,6 +1690,7 @@
 
         default: {
           return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                 << _.VkErrorID(4314)
                  << "Vulkan spec allows BuiltIn PointSize to be used only with "
                     "Vertex, TessellationControl, TessellationEvaluation or "
                     "Geometry execution models. "
@@ -1650,8 +1738,10 @@
       assert(function_id_ == 0);
       id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
           &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this,
-          "Vulkan spec doesn't allow BuiltIn Position to be used for variables "
-          "with Input storage class if execution model is Vertex.",
+          std::string(_.VkErrorID(4320) +
+                      "Vulkan spec doesn't allow BuiltIn Position to be used "
+                      "for variables "
+                      "with Input storage class if execution model is Vertex."),
           SpvExecutionModelVertex, decoration, built_in_inst,
           referenced_from_inst, std::placeholders::_1));
     }
@@ -1664,6 +1754,7 @@
                   [this, &referenced_from_inst](
                       const std::string& message) -> spv_result_t {
                     return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                           << _.VkErrorID(4321)
                            << "According to the Vulkan spec BuiltIn Position "
                               "variable needs to be a 4-component 32-bit float "
                               "vector. "
@@ -1690,6 +1781,7 @@
                         const std::string& message) -> spv_result_t {
                       return _.diag(SPV_ERROR_INVALID_DATA,
                                     &referenced_from_inst)
+                             << _.VkErrorID(4321)
                              << "According to the Vulkan spec BuiltIn Position "
                                 "variable needs to be a 4-component 32-bit "
                                 "float vector. "
@@ -1704,6 +1796,7 @@
                         const std::string& message) -> spv_result_t {
                       return _.diag(SPV_ERROR_INVALID_DATA,
                                     &referenced_from_inst)
+                             << _.VkErrorID(4321)
                              << "According to the Vulkan spec BuiltIn Position "
                                 "variable needs to be a 4-component 32-bit "
                                 "float vector. "
@@ -1717,6 +1810,7 @@
 
         default: {
           return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                 << _.VkErrorID(4318)
                  << "Vulkan spec allows BuiltIn Position to be used only "
                     "with Vertex, TessellationControl, TessellationEvaluation"
                     " or Geometry execution models. "
@@ -1788,6 +1882,7 @@
               decoration, inst,
               [this, &inst](const std::string& message) -> spv_result_t {
                 return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << _.VkErrorID(4337)
                        << "According to the Vulkan spec BuiltIn PrimitiveId "
                           "variable needs to be a 32-bit int scalar. "
                        << message;
@@ -1799,6 +1894,7 @@
               decoration, inst,
               [this, &inst](const std::string& message) -> spv_result_t {
                 return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << _.VkErrorID(4337)
                        << "According to the Vulkan spec BuiltIn PrimitiveId "
                           "variable needs to be a 32-bit int scalar. "
                        << message;
@@ -1833,23 +1929,29 @@
       assert(function_id_ == 0);
       id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
           &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this,
-          "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for "
-          "variables with Output storage class if execution model is "
-          "TessellationControl.",
+          std::string(
+              _.VkErrorID(4334) +
+              "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for "
+              "variables with Output storage class if execution model is "
+              "TessellationControl."),
           SpvExecutionModelTessellationControl, decoration, built_in_inst,
           referenced_from_inst, std::placeholders::_1));
       id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
           &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this,
-          "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for "
-          "variables with Output storage class if execution model is "
-          "TessellationEvaluation.",
+          std::string(
+              _.VkErrorID(4334) +
+              "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for "
+              "variables with Output storage class if execution model is "
+              "TessellationEvaluation."),
           SpvExecutionModelTessellationEvaluation, decoration, built_in_inst,
           referenced_from_inst, std::placeholders::_1));
       id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
           &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this,
-          "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for "
-          "variables with Output storage class if execution model is "
-          "Fragment.",
+          std::string(
+              _.VkErrorID(4334) +
+              "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for "
+              "variables with Output storage class if execution model is "
+              "Fragment."),
           SpvExecutionModelFragment, decoration, built_in_inst,
           referenced_from_inst, std::placeholders::_1));
     }
@@ -1873,6 +1975,7 @@
 
         default: {
           return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                 << _.VkErrorID(4330)
                  << "Vulkan spec allows BuiltIn PrimitiveId to be used only "
                     "with Fragment, TessellationControl, "
                     "TessellationEvaluation or Geometry execution models. "
@@ -1900,6 +2003,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4356)
                      << "According to the Vulkan spec BuiltIn SampleId "
                         "variable needs to be a 32-bit int scalar. "
                      << message;
@@ -1921,6 +2025,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4355)
              << "Vulkan spec allows BuiltIn SampleId to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1931,6 +2036,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4354)
                << "Vulkan spec allows BuiltIn SampleId to be used only with "
                   "Fragment execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1956,6 +2062,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4359)
                      << "According to the Vulkan spec BuiltIn SampleMask "
                         "variable needs to be a 32-bit int array. "
                      << message;
@@ -1978,6 +2085,7 @@
         storage_class != SpvStorageClassInput &&
         storage_class != SpvStorageClassOutput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4358)
              << "Vulkan spec allows BuiltIn SampleMask to be only used for "
                 "variables with Input or Output storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -1988,6 +2096,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4357)
                << "Vulkan spec allows BuiltIn SampleMask to be used only "
                   "with "
                   "Fragment execution model. "
@@ -2014,6 +2123,7 @@
             decoration, inst, 2,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4362)
                      << "According to the Vulkan spec BuiltIn SamplePosition "
                         "variable needs to be a 2-component 32-bit float "
                         "vector. "
@@ -2036,6 +2146,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4361)
              << "Vulkan spec allows BuiltIn SamplePosition to be only used "
                 "for "
                 "variables with Input storage class. "
@@ -2047,6 +2158,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelFragment) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4360)
                << "Vulkan spec allows BuiltIn SamplePosition to be used only "
                   "with "
                   "Fragment execution model. "
@@ -2073,6 +2185,7 @@
             decoration, inst, 3,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4389)
                      << "According to the Vulkan spec BuiltIn TessCoord "
                         "variable needs to be a 3-component 32-bit float "
                         "vector. "
@@ -2095,6 +2208,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4388)
              << "Vulkan spec allows BuiltIn TessCoord to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -2105,6 +2219,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelTessellationEvaluation) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4387)
                << "Vulkan spec allows BuiltIn TessCoord to be used only with "
                   "TessellationEvaluation execution model. "
                << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -2130,6 +2245,7 @@
             decoration, inst, 4,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4393)
                      << "According to the Vulkan spec BuiltIn TessLevelOuter "
                         "variable needs to be a 4-component 32-bit float "
                         "array. "
@@ -2150,6 +2266,7 @@
             decoration, inst, 2,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4397)
                      << "According to the Vulkan spec BuiltIn TessLevelOuter "
                         "variable needs to be a 2-component 32-bit float "
                         "array. "
@@ -2167,6 +2284,7 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
   if (spvIsVulkanEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
@@ -2175,7 +2293,7 @@
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
              << "Vulkan spec allows BuiltIn "
              << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                              decoration.params()[0])
+                                              operand)
              << " to be only used for variables with Input or Output storage "
                 "class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -2216,10 +2334,11 @@
         }
 
         default: {
+          uint32_t vuid = (operand == SpvBuiltInTessLevelOuter) ? 4390 : 4394;
           return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-                 << "Vulkan spec allows BuiltIn "
+                 << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
                  << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                                  decoration.params()[0])
+                                                  operand)
                  << " to be used only with TessellationControl or "
                     "TessellationEvaluation execution models. "
                  << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -2246,7 +2365,7 @@
             decoration, inst,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the "
+                     << _.VkErrorID(4400) << "According to the "
                      << spvLogStringForEnv(_.context()->target_env)
                      << " spec BuiltIn VertexIndex variable needs to be a "
                         "32-bit int scalar. "
@@ -2381,7 +2500,7 @@
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << spvLogStringForEnv(_.context()->target_env)
+             << _.VkErrorID(4399) << spvLogStringForEnv(_.context()->target_env)
              << " spec allows BuiltIn VertexIndex to be only used for "
                 "variables with Input storage class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -2392,6 +2511,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelVertex) {
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4398)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn VertexIndex to be used only with "
                   "Vertex execution model. "
@@ -2422,7 +2542,10 @@
               decoration, inst,
               [this, &decoration,
                &inst](const std::string& message) -> spv_result_t {
+                uint32_t vuid =
+                    (decoration.params()[0] == SpvBuiltInLayer) ? 4276 : 4408;
                 return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << _.VkErrorID(vuid)
                        << "According to the Vulkan spec BuiltIn "
                        << _.grammar().lookupOperandName(
                               SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0])
@@ -2436,7 +2559,10 @@
               decoration, inst,
               [this, &decoration,
                &inst](const std::string& message) -> spv_result_t {
+                uint32_t vuid =
+                    (decoration.params()[0] == SpvBuiltInLayer) ? 4276 : 4408;
                 return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                       << _.VkErrorID(vuid)
                        << "According to the Vulkan spec BuiltIn "
                        << _.grammar().lookupOperandName(
                               SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0])
@@ -2456,6 +2582,7 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
   if (spvIsVulkanEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
@@ -2464,7 +2591,7 @@
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
              << "Vulkan spec allows BuiltIn "
              << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                              decoration.params()[0])
+                                              operand)
              << " to be only used for variables with Input or Output storage "
                 "class. "
              << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -2513,20 +2640,35 @@
         case SpvExecutionModelVertex:
         case SpvExecutionModelTessellationEvaluation: {
           if (!_.HasCapability(SpvCapabilityShaderViewportIndexLayerEXT)) {
+            if (operand == SpvBuiltInViewportIndex &&
+                _.HasCapability(SpvCapabilityShaderViewportIndex))
+              break;  // Ok
+            if (operand == SpvBuiltInLayer &&
+                _.HasCapability(SpvCapabilityShaderLayer))
+              break;  // Ok
+
+            const char* capability = "ShaderViewportIndexLayerEXT";
+
+            if (operand == SpvBuiltInViewportIndex)
+              capability = "ShaderViewportIndexLayerEXT or ShaderViewportIndex";
+            if (operand == SpvBuiltInLayer)
+              capability = "ShaderViewportIndexLayerEXT or ShaderLayer";
+
             return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
                    << "Using BuiltIn "
                    << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                                    decoration.params()[0])
-                   << " in Vertex or Tessellation execution model requires "
-                      "the ShaderViewportIndexLayerEXT capability.";
+                                                    operand)
+                   << " in Vertex or Tessellation execution model requires the "
+                   << capability << " capability.";
           }
           break;
         }
         default: {
+          uint32_t vuid = (operand == SpvBuiltInLayer) ? 4272 : 4404;
           return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-                 << "Vulkan spec allows BuiltIn "
+                 << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
                  << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                                  decoration.params()[0])
+                                                  operand)
                  << " to be used only with Vertex, TessellationEvaluation, "
                     "Geometry, or Fragment execution models. "
                  << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
@@ -2554,12 +2696,28 @@
             decoration, inst, 3,
             [this, &decoration,
              &inst](const std::string& message) -> spv_result_t {
+              uint32_t operand = decoration.params()[0];
+              uint32_t vuid = 0;
+              switch (operand) {
+                case SpvBuiltInGlobalInvocationId:
+                  vuid = 4238;
+                  break;
+                case SpvBuiltInLocalInvocationId:
+                  vuid = 4283;
+                  break;
+                case SpvBuiltInNumWorkgroups:
+                  vuid = 4298;
+                  break;
+                case SpvBuiltInWorkgroupId:
+                  vuid = 4424;
+                  break;
+              };
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the "
+                     << _.VkErrorID(vuid) << "According to the "
                      << spvLogStringForEnv(_.context()->target_env)
                      << " spec BuiltIn "
                      << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
-                                                      decoration.params()[0])
+                                                      operand)
                      << " variable needs to be a 3-component 32-bit int "
                         "vector. "
                      << message;
@@ -2577,12 +2735,28 @@
     const Decoration& decoration, const Instruction& built_in_inst,
     const Instruction& referenced_inst,
     const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
   if (spvIsVulkanOrWebGPUEnv(_.context()->target_env)) {
     const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
     if (storage_class != SpvStorageClassMax &&
         storage_class != SpvStorageClassInput) {
+      uint32_t vuid = 0;
+      switch (operand) {
+        case SpvBuiltInGlobalInvocationId:
+          vuid = 4237;
+          break;
+        case SpvBuiltInLocalInvocationId:
+          vuid = 4282;
+          break;
+        case SpvBuiltInNumWorkgroups:
+          vuid = 4297;
+          break;
+        case SpvBuiltInWorkgroupId:
+          vuid = 4423;
+          break;
+      };
       return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
-             << spvLogStringForEnv(_.context()->target_env)
+             << _.VkErrorID(vuid) << spvLogStringForEnv(_.context()->target_env)
              << " spec allows BuiltIn "
              << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
                                               decoration.params()[0])
@@ -2599,7 +2773,23 @@
       bool has_webgpu_model = execution_model == SpvExecutionModelGLCompute;
       if ((spvIsVulkanEnv(_.context()->target_env) && !has_vulkan_model) ||
           (spvIsWebGPUEnv(_.context()->target_env) && !has_webgpu_model)) {
+        uint32_t vuid = 0;
+        switch (operand) {
+          case SpvBuiltInGlobalInvocationId:
+            vuid = 4236;
+            break;
+          case SpvBuiltInLocalInvocationId:
+            vuid = 4281;
+            break;
+          case SpvBuiltInNumWorkgroups:
+            vuid = 4296;
+            break;
+          case SpvBuiltInWorkgroupId:
+            vuid = 4422;
+            break;
+        };
         return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(vuid)
                << spvLogStringForEnv(_.context()->target_env)
                << " spec allows BuiltIn "
                << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
@@ -2793,6 +2983,7 @@
     if (spvIsVulkanEnv(_.context()->target_env) &&
         !spvOpcodeIsConstant(inst.opcode())) {
       return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+             << _.VkErrorID(4426)
              << "Vulkan spec requires BuiltIn WorkgroupSize to be a "
                 "constant. "
              << GetIdDesc(inst) << " is not a constant.";
@@ -2802,7 +2993,7 @@
             decoration, inst, 3,
             [this, &inst](const std::string& message) -> spv_result_t {
               return _.diag(SPV_ERROR_INVALID_DATA, &inst)
-                     << "According to the "
+                     << _.VkErrorID(4427) << "According to the "
                      << spvLogStringForEnv(_.context()->target_env)
                      << " spec BuiltIn WorkgroupSize variable needs to be a "
                         "3-component 32-bit int vector. "
@@ -2824,6 +3015,7 @@
     for (const SpvExecutionModel execution_model : execution_models_) {
       if (execution_model != SpvExecutionModelGLCompute) {
         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,
@@ -2845,6 +3037,259 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              uint32_t vuid = (decoration.params()[0] == SpvBuiltInBaseInstance)
+                                  ? 4183
+                                  : 4186;
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(vuid)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateBaseInstanceOrVertexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4182 : 4185;
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model != SpvExecutionModelVertex) {
+        uint32_t vuid = (operand == SpvBuiltInBaseInstance) ? 4181 : 4184;
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(vuid) << "Vulkan spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                operand)
+               << " to be used only with Vertex execution model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
+        std::bind(&BuiltInsValidator::ValidateBaseInstanceOrVertexAtReference,
+                  this, decoration, built_in_inst, referenced_from_inst,
+                  std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateDrawIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4209)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateDrawIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateDrawIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4208) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model != SpvExecutionModelVertex &&
+          execution_model != SpvExecutionModelMeshNV &&
+          execution_model != SpvExecutionModelTaskNV) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4207) << "Vulkan spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                operand)
+               << " to be used only with Vertex, MeshNV, or TaskNV execution "
+                  "model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateDrawIndexAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateViewIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4403)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateViewIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateViewIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4402) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model == SpvExecutionModelGLCompute) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4401) << "Vulkan spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                operand)
+               << " to be not be used with GLCompute execution model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateViewIndexAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateDeviceIndexAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4206)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  return ValidateDeviceIndexAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateDeviceIndexAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  uint32_t operand = decoration.params()[0];
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4205) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              operand)
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateDeviceIndexAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t BuiltInsValidator::ValidateSMBuiltinsAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   if (spvIsVulkanEnv(_.context()->target_env)) {
@@ -2900,6 +3345,142 @@
   return SPV_SUCCESS;
 }
 
+spv_result_t BuiltInsValidator::ValidatePrimitiveShadingRateAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4486)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidatePrimitiveShadingRateAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidatePrimitiveShadingRateAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassOutput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4485) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " to be only used for variables with Output storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      switch (execution_model) {
+        case SpvExecutionModelVertex:
+        case SpvExecutionModelGeometry:
+        case SpvExecutionModelMeshNV:
+          break;
+        default: {
+          return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+                 << _.VkErrorID(4484) << "Vulkan spec allows BuiltIn "
+                 << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                  decoration.params()[0])
+                 << " to be used only with Vertex, Geometry, or MeshNV "
+                    "execution models. "
+                 << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                     referenced_from_inst, execution_model);
+        }
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(
+        std::bind(&BuiltInsValidator::ValidatePrimitiveShadingRateAtReference,
+                  this, decoration, built_in_inst, referenced_from_inst,
+                  std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t BuiltInsValidator::ValidateShadingRateAtDefinition(
+    const Decoration& decoration, const Instruction& inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    if (spv_result_t error = ValidateI32(
+            decoration, inst,
+            [this, &inst,
+             &decoration](const std::string& message) -> spv_result_t {
+              return _.diag(SPV_ERROR_INVALID_DATA, &inst)
+                     << _.VkErrorID(4492)
+                     << "According to the Vulkan spec BuiltIn "
+                     << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                      decoration.params()[0])
+                     << " variable needs to be a 32-bit int scalar. "
+                     << message;
+            })) {
+      return error;
+    }
+  }
+
+  // Seed at reference checks with this built-in.
+  return ValidateShadingRateAtReference(decoration, inst, inst, inst);
+}
+
+spv_result_t BuiltInsValidator::ValidateShadingRateAtReference(
+    const Decoration& decoration, const Instruction& built_in_inst,
+    const Instruction& referenced_inst,
+    const Instruction& referenced_from_inst) {
+  if (spvIsVulkanEnv(_.context()->target_env)) {
+    const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst);
+    if (storage_class != SpvStorageClassMax &&
+        storage_class != SpvStorageClassInput) {
+      return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+             << _.VkErrorID(4491) << "Vulkan spec allows BuiltIn "
+             << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                              decoration.params()[0])
+             << " to be only used for variables with Input storage class. "
+             << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                 referenced_from_inst)
+             << " " << GetStorageClassDesc(referenced_from_inst);
+    }
+
+    for (const SpvExecutionModel execution_model : execution_models_) {
+      if (execution_model != SpvExecutionModelFragment) {
+        return _.diag(SPV_ERROR_INVALID_DATA, &referenced_from_inst)
+               << _.VkErrorID(4490) << "Vulkan spec allows BuiltIn "
+               << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN,
+                                                decoration.params()[0])
+               << " to be used only with the Fragment execution model. "
+               << GetReferenceDesc(decoration, built_in_inst, referenced_inst,
+                                   referenced_from_inst, execution_model);
+      }
+    }
+  }
+
+  if (function_id_ == 0) {
+    // Propagate this rule to all dependant ids in the global scope.
+    id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind(
+        &BuiltInsValidator::ValidateShadingRateAtReference, this, decoration,
+        built_in_inst, referenced_from_inst, std::placeholders::_1));
+  }
+
+  return SPV_SUCCESS;
+}
+
 spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition(
     const Decoration& decoration, const Instruction& inst) {
   const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]);
@@ -3035,6 +3616,19 @@
     case SpvBuiltInSMIDNV: {
       return ValidateSMBuiltinsAtDefinition(decoration, inst);
     }
+    case SpvBuiltInBaseInstance:
+    case SpvBuiltInBaseVertex: {
+      return ValidateBaseInstanceOrVertexAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInDrawIndex: {
+      return ValidateDrawIndexAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInViewIndex: {
+      return ValidateViewIndexAtDefinition(decoration, inst);
+    }
+    case SpvBuiltInDeviceIndex: {
+      return ValidateDeviceIndexAtDefinition(decoration, inst);
+    }
     case SpvBuiltInWorkDim:
     case SpvBuiltInGlobalSize:
     case SpvBuiltInEnqueuedWorkgroupSize:
@@ -3042,11 +3636,6 @@
     case SpvBuiltInGlobalLinearId:
     case SpvBuiltInSubgroupMaxSize:
     case SpvBuiltInNumEnqueuedSubgroups:
-    case SpvBuiltInBaseVertex:
-    case SpvBuiltInBaseInstance:
-    case SpvBuiltInDrawIndex:
-    case SpvBuiltInDeviceIndex:
-    case SpvBuiltInViewIndex:
     case SpvBuiltInBaryCoordNoPerspAMD:
     case SpvBuiltInBaryCoordNoPerspCentroidAMD:
     case SpvBuiltInBaryCoordNoPerspSampleAMD:
@@ -3092,6 +3681,11 @@
     case SpvBuiltInRayGeometryIndexKHR: {
       // No validation rules (for the moment).
       break;
+
+      case SpvBuiltInPrimitiveShadingRateKHR:
+        return ValidatePrimitiveShadingRateAtDefinition(decoration, inst);
+      case SpvBuiltInShadingRateKHR:
+        return ValidateShadingRateAtDefinition(decoration, inst);
     }
   }
   return SPV_SUCCESS;
diff --git a/source/val/validate_capability.cpp b/source/val/validate_capability.cpp
index 8a356bf..4b98bc1 100644
--- a/source/val/validate_capability.cpp
+++ b/source/val/validate_capability.cpp
@@ -14,8 +14,6 @@
 
 // Validates OpCapability instruction.
 
-#include "source/val/validate.h"
-
 #include <cassert>
 #include <string>
 #include <unordered_set>
@@ -23,6 +21,7 @@
 #include "source/diagnostic.h"
 #include "source/opcode.h"
 #include "source/val/instruction.h"
+#include "source/val/validate.h"
 #include "source/val/validation_state.h"
 
 namespace spvtools {
@@ -166,7 +165,6 @@
   switch (capability) {
     case SpvCapabilityAddresses:
     case SpvCapabilityFloat16Buffer:
-    case SpvCapabilityGroups:
     case SpvCapabilityInt16:
     case SpvCapabilityInt8:
     case SpvCapabilityKernel:
@@ -175,8 +173,6 @@
       return true;
     case SpvCapabilityInt64:
       return !embedded_profile;
-    case SpvCapabilityPipes:
-      return embedded_profile;
   }
   return false;
 }
@@ -187,6 +183,7 @@
   switch (capability) {
     case SpvCapabilityDeviceEnqueue:
     case SpvCapabilityGenericPointer:
+    case SpvCapabilityGroups:
     case SpvCapabilityPipes:
       return true;
   }
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index 8eb3a96..8babd35 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -89,6 +89,8 @@
            << block->predecessors()->size() << ").";
   }
 
+  std::unordered_set<uint32_t> observed_predecessors;
+
   for (size_t i = 3; i < inst->words().size(); ++i) {
     auto inc_id = inst->word(i);
     if (i % 2 == 1) {
@@ -115,6 +117,17 @@
                << " is not a predecessor of <id> " << _.getIdName(block->id())
                << ".";
       }
+
+      // We must not have already seen this predecessor as one of the phi's
+      // operands.
+      if (observed_predecessors.count(inc_id) != 0) {
+        return _.diag(SPV_ERROR_INVALID_ID, inst)
+               << "OpPhi references incoming basic block <id> "
+               << _.getIdName(inc_id) << " multiple times.";
+      }
+
+      // Note the fact that we have now observed this predecessor.
+      observed_predecessors.insert(inc_id);
     }
   }
 
diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp
index 7ce681c..17b0446 100644
--- a/source/val/validate_extensions.cpp
+++ b/source/val/validate_extensions.cpp
@@ -13,11 +13,13 @@
 // limitations under the License.
 
 // Validates correctness of extension SPIR-V instructions.
-
+#include <cstdlib>
 #include <sstream>
 #include <string>
 #include <vector>
 
+#include "spirv/unified1/NonSemanticClspvReflection.h"
+
 #include "OpenCLDebugInfo100.h"
 #include "source/diagnostic.h"
 #include "source/enum_string_mapping.h"
@@ -79,6 +81,7 @@
     const ValidationState_t& _,
     const std::function<bool(OpenCLDebugInfo100Instructions)>& 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 ||
@@ -165,11 +168,18 @@
 spv_result_t ValidateOperandDebugType(
     ValidationState_t& _, const std::string& debug_inst_name,
     const Instruction* inst, uint32_t word_index,
-    const std::function<std::string()>& ext_inst_name) {
+    const std::function<std::string()>& ext_inst_name,
+    bool allow_template_param) {
   std::function<bool(OpenCLDebugInfo100Instructions)> expectation =
-      [](OpenCLDebugInfo100Instructions dbg_inst) {
+      [&allow_template_param](OpenCLDebugInfo100Instructions dbg_inst) {
+        if (allow_template_param &&
+            (dbg_inst == OpenCLDebugInfo100DebugTypeTemplateParameter ||
+             dbg_inst ==
+                 OpenCLDebugInfo100DebugTypeTemplateTemplateParameter)) {
+          return true;
+        }
         return OpenCLDebugInfo100DebugTypeBasic <= dbg_inst &&
-               dbg_inst <= OpenCLDebugInfo100DebugTypePtrToMember;
+               dbg_inst <= OpenCLDebugInfo100DebugTypeTemplate;
       };
   if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
     return SPV_SUCCESS;
@@ -180,6 +190,499 @@
          << " 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);
+  const auto kernel = _.FindDef(kernel_id);
+  if (kernel->opcode() != SpvOpFunction) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Kernel does not reference a function";
+  }
+
+  bool found_kernel = false;
+  for (auto entry_point : _.entry_points()) {
+    if (entry_point == kernel_id) {
+      found_kernel = true;
+      break;
+    }
+  }
+  if (!found_kernel) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Kernel does not reference an entry-point";
+  }
+
+  const auto* exec_models = _.GetExecutionModels(kernel_id);
+  if (!exec_models || exec_models->empty()) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Kernel does not reference an entry-point";
+  }
+  for (auto exec_model : *exec_models) {
+    if (exec_model != SpvExecutionModelGLCompute) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "Kernel must refer only to GLCompute entry-points";
+    }
+  }
+
+  auto name = _.FindDef(inst->GetOperandAs<uint32_t>(5));
+  if (!name || name->opcode() != SpvOpString) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst) << "Name must be an OpString";
+  }
+
+  const std::string name_str = reinterpret_cast<const char*>(
+      name->words().data() + name->operands()[1].offset);
+  bool found = false;
+  for (auto& desc : _.entry_point_descriptions(kernel_id)) {
+    if (name_str == desc.name) {
+      found = true;
+      break;
+    }
+  }
+  if (!found) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Name must match an entry-point for Kernel";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionArgumentInfo(ValidationState_t& _,
+                                                 const Instruction* inst) {
+  const auto num_operands = inst->operands().size();
+  if (_.GetIdOpcode(inst->GetOperandAs<uint32_t>(4)) != SpvOpString) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst) << "Name must be an OpString";
+  }
+  if (num_operands > 5) {
+    if (_.GetIdOpcode(inst->GetOperandAs<uint32_t>(5)) != SpvOpString) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "TypeName must be an OpString";
+    }
+  }
+  if (num_operands > 6) {
+    if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "AddressQualifier must be a 32-bit unsigned integer "
+                "OpConstant";
+    }
+  }
+  if (num_operands > 7) {
+    if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "AccessQualifier must be a 32-bit unsigned integer "
+                "OpConstant";
+    }
+  }
+  if (num_operands > 8) {
+    if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(8))) {
+      return _.diag(SPV_ERROR_INVALID_ID, inst)
+             << "TypeQualifier must be a 32-bit unsigned integer "
+                "OpConstant";
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateKernelDecl(ValidationState_t& _, const Instruction* inst) {
+  const auto decl_id = inst->GetOperandAs<uint32_t>(4);
+  const auto decl = _.FindDef(decl_id);
+  if (!decl || decl->opcode() != SpvOpExtInst) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Kernel must be a Kernel extended instruction";
+  }
+
+  if (decl->GetOperandAs<uint32_t>(2) != inst->GetOperandAs<uint32_t>(2)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Kernel must be from the same extended instruction import";
+  }
+
+  const auto ext_inst =
+      decl->GetOperandAs<NonSemanticClspvReflectionInstructions>(3);
+  if (ext_inst != NonSemanticClspvReflectionKernel) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Kernel must be a Kernel extended instruction";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateArgInfo(ValidationState_t& _, const Instruction* inst,
+                             uint32_t info_index) {
+  auto info = _.FindDef(inst->GetOperandAs<uint32_t>(info_index));
+  if (!info || info->opcode() != SpvOpExtInst) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "ArgInfo must be an ArgumentInfo extended instruction";
+  }
+
+  if (info->GetOperandAs<uint32_t>(2) != inst->GetOperandAs<uint32_t>(2)) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "ArgInfo must be from the same extended instruction import";
+  }
+
+  auto ext_inst = info->GetOperandAs<NonSemanticClspvReflectionInstructions>(3);
+  if (ext_inst != NonSemanticClspvReflectionArgumentInfo) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "ArgInfo must be an ArgumentInfo extended instruction";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionArgumentBuffer(ValidationState_t& _,
+                                                   const Instruction* inst) {
+  const auto num_operands = inst->operands().size();
+  if (auto error = ValidateKernelDecl(_, inst)) {
+    return error;
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Ordinal must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "DescriptorSet must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Binding must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (num_operands == 9) {
+    if (auto error = ValidateArgInfo(_, inst, 8)) {
+      return error;
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionArgumentPodBuffer(ValidationState_t& _,
+                                                      const Instruction* inst) {
+  const auto num_operands = inst->operands().size();
+  if (auto error = ValidateKernelDecl(_, inst)) {
+    return error;
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Ordinal must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "DescriptorSet must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Binding must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(8))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Offset must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(9))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Size must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (num_operands == 11) {
+    if (auto error = ValidateArgInfo(_, inst, 10)) {
+      return error;
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionArgumentPodPushConstant(
+    ValidationState_t& _, const Instruction* inst) {
+  const auto num_operands = inst->operands().size();
+  if (auto error = ValidateKernelDecl(_, inst)) {
+    return error;
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Ordinal must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Offset must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Size must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (num_operands == 9) {
+    if (auto error = ValidateArgInfo(_, inst, 8)) {
+      return error;
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionArgumentWorkgroup(ValidationState_t& _,
+                                                      const Instruction* inst) {
+  const auto num_operands = inst->operands().size();
+  if (auto error = ValidateKernelDecl(_, inst)) {
+    return error;
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Ordinal must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "SpecId must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "ElemSize must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (num_operands == 9) {
+    if (auto error = ValidateArgInfo(_, inst, 8)) {
+      return error;
+    }
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionSpecConstantTriple(
+    ValidationState_t& _, const Instruction* inst) {
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "X must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Y must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Z must be a 32-bit unsigned integer OpConstant";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionSpecConstantWorkDim(
+    ValidationState_t& _, const Instruction* inst) {
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Dim must be a 32-bit unsigned integer OpConstant";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionPushConstant(ValidationState_t& _,
+                                                 const Instruction* inst) {
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Offset must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Size must be a 32-bit unsigned integer OpConstant";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionConstantData(ValidationState_t& _,
+                                                 const Instruction* inst) {
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "DescriptorSet must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Binding must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (_.GetIdOpcode(inst->GetOperandAs<uint32_t>(6)) != SpvOpString) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst) << "Data must be an OpString";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionSampler(ValidationState_t& _,
+                                            const Instruction* inst) {
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(4))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "DescriptorSet must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Binding must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Mask must be a 32-bit unsigned integer OpConstant";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionPropertyRequiredWorkgroupSize(
+    ValidationState_t& _, const Instruction* inst) {
+  if (auto error = ValidateKernelDecl(_, inst)) {
+    return error;
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(5))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "X must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(6))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Y must be a 32-bit unsigned integer OpConstant";
+  }
+
+  if (!IsUint32Constant(_, inst->GetOperandAs<uint32_t>(7))) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Z must be a 32-bit unsigned integer OpConstant";
+  }
+
+  return SPV_SUCCESS;
+}
+
+spv_result_t ValidateClspvReflectionInstruction(ValidationState_t& _,
+                                                const Instruction* inst,
+                                                uint32_t /*version*/) {
+  if (!_.IsVoidType(inst->type_id())) {
+    return _.diag(SPV_ERROR_INVALID_ID, inst)
+           << "Return Type must be OpTypeVoid";
+  }
+
+  auto ext_inst = inst->GetOperandAs<NonSemanticClspvReflectionInstructions>(3);
+  switch (ext_inst) {
+    case NonSemanticClspvReflectionKernel:
+      return ValidateClspvReflectionKernel(_, inst);
+    case NonSemanticClspvReflectionArgumentInfo:
+      return ValidateClspvReflectionArgumentInfo(_, inst);
+    case NonSemanticClspvReflectionArgumentStorageBuffer:
+    case NonSemanticClspvReflectionArgumentUniform:
+    case NonSemanticClspvReflectionArgumentSampledImage:
+    case NonSemanticClspvReflectionArgumentStorageImage:
+    case NonSemanticClspvReflectionArgumentSampler:
+      return ValidateClspvReflectionArgumentBuffer(_, inst);
+    case NonSemanticClspvReflectionArgumentPodStorageBuffer:
+    case NonSemanticClspvReflectionArgumentPodUniform:
+      return ValidateClspvReflectionArgumentPodBuffer(_, inst);
+    case NonSemanticClspvReflectionArgumentPodPushConstant:
+      return ValidateClspvReflectionArgumentPodPushConstant(_, inst);
+    case NonSemanticClspvReflectionArgumentWorkgroup:
+      return ValidateClspvReflectionArgumentWorkgroup(_, inst);
+    case NonSemanticClspvReflectionSpecConstantWorkgroupSize:
+    case NonSemanticClspvReflectionSpecConstantGlobalOffset:
+      return ValidateClspvReflectionSpecConstantTriple(_, inst);
+    case NonSemanticClspvReflectionSpecConstantWorkDim:
+      return ValidateClspvReflectionSpecConstantWorkDim(_, inst);
+    case NonSemanticClspvReflectionPushConstantGlobalOffset:
+    case NonSemanticClspvReflectionPushConstantEnqueuedLocalSize:
+    case NonSemanticClspvReflectionPushConstantGlobalSize:
+    case NonSemanticClspvReflectionPushConstantRegionOffset:
+    case NonSemanticClspvReflectionPushConstantNumWorkgroups:
+    case NonSemanticClspvReflectionPushConstantRegionGroupOffset:
+      return ValidateClspvReflectionPushConstant(_, inst);
+    case NonSemanticClspvReflectionConstantDataStorageBuffer:
+    case NonSemanticClspvReflectionConstantDataUniform:
+      return ValidateClspvReflectionConstantData(_, inst);
+    case NonSemanticClspvReflectionLiteralSampler:
+      return ValidateClspvReflectionSampler(_, inst);
+    case NonSemanticClspvReflectionPropertyRequiredWorkgroupSize:
+      return ValidateClspvReflectionPropertyRequiredWorkgroupSize(_, inst);
+    default:
+      break;
+  }
+
+  return SPV_SUCCESS;
+}
+
+bool IsConstIntScalarTypeWith32Or64Bits(ValidationState_t& _,
+                                        Instruction* instr) {
+  if (instr->opcode() != SpvOpConstant) return false;
+  if (!_.IsIntScalarType(instr->type_id())) return false;
+  uint32_t size_in_bits = _.GetBitWidth(instr->type_id());
+  return size_in_bits == 32 || size_in_bits == 64;
+}
+
+bool IsConstWithIntScalarType(ValidationState_t& _, const Instruction* inst,
+                              uint32_t word_index) {
+  auto* int_scalar_const = _.FindDef(inst->word(word_index));
+  if (int_scalar_const->opcode() == SpvOpConstant &&
+      _.IsIntScalarType(int_scalar_const->type_id())) {
+    return true;
+  }
+  return false;
+}
+
+bool IsDebugVariableWithIntScalarType(ValidationState_t& _,
+                                      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) {
+    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;
+    }
+  }
+  return false;
+}
+
 }  // anonymous namespace
 
 spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) {
@@ -2222,17 +2725,53 @@
         break;
       }
       case OpenCLDebugInfo100DebugTypeArray: {
-        auto validate_base_type =
-            ValidateOperandDebugType(_, "Base Type", inst, 5, ext_inst_name);
+        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) {
-          CHECK_OPERAND("Component Count", SpvOpConstant, i);
+          bool invalid = false;
           auto* component_count = _.FindDef(inst->word(i));
-          if (!_.IsIntScalarType(component_count->type_id()) ||
-              !component_count->word(3)) {
+          if (IsConstIntScalarTypeWith32Or64Bits(_, component_count)) {
+            // TODO: We need a spec discussion for the bindless array.
+            if (!component_count->word(3)) {
+              invalid = true;
+            }
+          } 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) {
+                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 positive "
-                   << "integer";
+                   << 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;
@@ -2250,14 +2789,16 @@
       }
       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);
+              _, "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);
+              _, "Parameter Types", inst, word_index, ext_inst_name, true);
           if (validate_param != SPV_SUCCESS) return validate_param;
         }
         break;
@@ -2271,7 +2812,7 @@
                 },
                 inst, 6)) {
           auto validate_underlying_type = ValidateOperandDebugType(
-              _, "Underlying Types", inst, 6, ext_inst_name);
+              _, "Underlying Types", inst, 6, ext_inst_name, false);
           if (validate_underlying_type != SPV_SUCCESS)
             return validate_underlying_type;
         }
@@ -2328,8 +2869,10 @@
       }
       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);
+            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);
@@ -2367,18 +2910,13 @@
       case OpenCLDebugInfo100DebugFunction: {
         CHECK_OPERAND("Name", SpvOpString, 5);
         auto validate_type =
-            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
+            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);
-        // TODO: The current OpenCL.100.DebugInfo spec says "Function
-        // is an OpFunction which is described by this instruction.".
-        // However, the function definition can be opted-out e.g.,
-        // inlining. We assume that Function operand can be a
-        // DebugInfoNone, but we must discuss it and update the spec.
         if (!DoesDebugInfoOperandMatchExpectation(
                 _,
                 [](OpenCLDebugInfo100Instructions dbg_inst) {
@@ -2396,7 +2934,7 @@
       case OpenCLDebugInfo100DebugFunctionDeclaration: {
         CHECK_OPERAND("Name", SpvOpString, 5);
         auto validate_type =
-            ValidateOperandDebugType(_, "Type", inst, 6, ext_inst_name);
+            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 =
@@ -2414,9 +2952,6 @@
         break;
       }
       case OpenCLDebugInfo100DebugScope: {
-        // TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We are
-        // still in spec discussion about what must be "Scope" operand of
-        // DebugScope. Update this code if the conclusion is different.
         auto validate_scope =
             ValidateOperandLexicalScope(_, "Scope", inst, 5, ext_inst_name);
         if (validate_scope != SPV_SUCCESS) return validate_scope;
@@ -2428,8 +2963,10 @@
       }
       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);
+            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 =
@@ -2440,11 +2977,6 @@
       case OpenCLDebugInfo100DebugDeclare: {
         CHECK_DEBUG_OPERAND("Local Variable",
                             OpenCLDebugInfo100DebugLocalVariable, 5);
-
-        // TODO: We must discuss DebugDeclare.Variable of OpenCL.100.DebugInfo.
-        // Currently, it says "Variable must be an id of OpVariable instruction
-        // which defines the local variable.", but we want to allow
-        // OpFunctionParameter as well.
         auto* operand = _.FindDef(inst->word(6));
         if (operand->opcode() != SpvOpVariable &&
             operand->opcode() != SpvOpFunctionParameter) {
@@ -2464,18 +2996,120 @@
         }
         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)) {
+            return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                   << ext_inst_name() << ": "
+                   << "expected operand Parameters must be "
+                   << "DebugTypeTemplateParameter or "
+                   << "DebugTypeTemplateTemplateParameter";
+          }
+        }
+        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);
+        }
+        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));
+          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",
+                              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)) {
+            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 OpenCLDebugInfo100DebugTypePtrToMember:
-      case OpenCLDebugInfo100DebugTypeTemplate:
-      case OpenCLDebugInfo100DebugTypeTemplateParameter:
       case OpenCLDebugInfo100DebugTypeTemplateTemplateParameter:
       case OpenCLDebugInfo100DebugTypeTemplateParameterPack:
-      case OpenCLDebugInfo100DebugGlobalVariable:
       case OpenCLDebugInfo100DebugLexicalBlockDiscriminator:
-      case OpenCLDebugInfo100DebugInlinedAt:
       case OpenCLDebugInfo100DebugInlinedVariable:
-      case OpenCLDebugInfo100DebugValue:
       case OpenCLDebugInfo100DebugMacroDef:
       case OpenCLDebugInfo100DebugMacroUndef:
       case OpenCLDebugInfo100DebugImportedEntity:
@@ -2484,6 +3118,30 @@
         assert(0);
         break;
     }
+  } else if (ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) {
+    auto import_inst = _.FindDef(inst->GetOperandAs<uint32_t>(2));
+    const std::string name(reinterpret_cast<const char*>(
+        import_inst->words().data() + import_inst->operands()[1].offset));
+    const std::string reflection = "NonSemantic.ClspvReflection.";
+    char* end_ptr;
+    auto version_string = name.substr(reflection.size());
+    if (version_string.empty()) {
+      return _.diag(SPV_ERROR_INVALID_DATA, import_inst)
+             << "Missing NonSemantic.ClspvReflection import version";
+    }
+    uint32_t version = static_cast<uint32_t>(
+        std::strtoul(version_string.c_str(), &end_ptr, 10));
+    if (end_ptr && *end_ptr != '\0') {
+      return _.diag(SPV_ERROR_INVALID_DATA, import_inst)
+             << "NonSemantic.ClspvReflection import does not encode the "
+                "version correctly";
+    }
+    if (version == 0 || version > NonSemanticClspvReflectionRevision) {
+      return _.diag(SPV_ERROR_INVALID_DATA, import_inst)
+             << "Unknown NonSemantic.ClspvReflection import version";
+    }
+
+    return ValidateClspvReflectionInstruction(_, inst, version);
   }
 
   return SPV_SUCCESS;
diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp
index 9ce74a3..299a3ef 100644
--- a/source/val/validate_image.cpp
+++ b/source/val/validate_image.cpp
@@ -1,4 +1,6 @@
 // Copyright (c) 2017 Google Inc.
+// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
+// reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -33,7 +35,7 @@
 // Performs compile time check that all SpvImageOperandsXXX cases are handled in
 // this module. If SpvImageOperandsXXX list changes, this function will fail the
 // build.
-// For all other purposes this is a dummy function.
+// For all other purposes this is a placeholder function.
 bool CheckAllImageOperandsHandled() {
   SpvImageOperandsMask enum_val = SpvImageOperandsBiasMask;
 
@@ -737,7 +739,9 @@
   if (spvIsVulkanEnv(_.context()->target_env)) {
     if ((!_.IsFloatScalarType(info.sampled_type) &&
          !_.IsIntScalarType(info.sampled_type)) ||
-        32 != _.GetBitWidth(info.sampled_type)) {
+        (32 != _.GetBitWidth(info.sampled_type) &&
+         (64 != _.GetBitWidth(info.sampled_type) ||
+          !_.HasCapability(SpvCapabilityInt64ImageEXT)))) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << "Expected Sampled Type to be a 32-bit int or float "
                 "scalar type for Vulkan environment";
diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp
index 833734f..d16d48e 100644
--- a/source/val/validate_interfaces.cpp
+++ b/source/val/validate_interfaces.cpp
@@ -437,6 +437,19 @@
 
 spv_result_t ValidateLocations(ValidationState_t& _,
                                const Instruction* entry_point) {
+  // According to Vulkan 14.1 only the following execution models have
+  // locations assigned.
+  switch (entry_point->GetOperandAs<SpvExecutionModel>(0)) {
+    case SpvExecutionModelVertex:
+    case SpvExecutionModelTessellationControl:
+    case SpvExecutionModelTessellationEvaluation:
+    case SpvExecutionModelGeometry:
+    case SpvExecutionModelFragment:
+      break;
+    default:
+      return SPV_SUCCESS;
+  }
+
   // Locations are stored as a combined location and component values.
   std::unordered_set<uint32_t> input_locations;
   std::unordered_set<uint32_t> output_locations_index0;
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp
index 707ad52..b53f991 100644
--- a/source/val/validate_layout.cpp
+++ b/source/val/validate_layout.cpp
@@ -107,6 +107,11 @@
   }
 
   while (_.IsOpcodeInCurrentLayoutSection(opcode) == false) {
+    if (_.IsOpcodeInPreviousLayoutSection(opcode)) {
+      return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
+             << spvOpcodeString(opcode) << " is in an invalid layout section";
+    }
+
     _.ProgressToNextLayoutSectionOrder();
 
     switch (_.current_layout_section()) {
@@ -135,6 +140,20 @@
 // encountered inside of a function.
 spv_result_t FunctionScopedInstructions(ValidationState_t& _,
                                         const Instruction* inst, SpvOp opcode) {
+  // Make sure we advance into the function definitions when we hit
+  // non-function declaration instructions.
+  if (_.current_layout_section() == kLayoutFunctionDeclarations &&
+      !_.IsOpcodeInCurrentLayoutSection(opcode)) {
+    _.ProgressToNextLayoutSectionOrder();
+
+    if (_.in_function_body()) {
+      if (auto error = _.current_function().RegisterSetFunctionDeclType(
+              FunctionDecl::kFunctionDeclDefinition)) {
+        return error;
+      }
+    }
+  }
+
   if (_.IsOpcodeInCurrentLayoutSection(opcode)) {
     switch (opcode) {
       case SpvOpFunction: {
@@ -208,12 +227,6 @@
           return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                  << "A block must end with a branch instruction.";
         }
-        if (_.current_layout_section() == kLayoutFunctionDeclarations) {
-          _.ProgressToNextLayoutSectionOrder();
-          if (auto error = _.current_function().RegisterSetFunctionDeclType(
-                  FunctionDecl::kFunctionDeclDefinition))
-            return error;
-        }
         break;
 
       case SpvOpExtInst:
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 0739148..e190b3c 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -30,114 +30,74 @@
 namespace val {
 namespace {
 
-bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) {
+ModuleLayoutSection InstructionLayoutSection(
+    ModuleLayoutSection current_section, SpvOp op) {
   // See Section 2.4
-  bool out = false;
-  // clang-format off
-  switch (layout) {
-    case kLayoutCapabilities:  out = op == SpvOpCapability;    break;
-    case kLayoutExtensions:    out = op == SpvOpExtension;     break;
-    case kLayoutExtInstImport: out = op == SpvOpExtInstImport; break;
-    case kLayoutMemoryModel:   out = op == SpvOpMemoryModel;   break;
-    case kLayoutEntryPoint:    out = op == SpvOpEntryPoint;    break;
-    case kLayoutExecutionMode:
-      out = op == SpvOpExecutionMode || op == SpvOpExecutionModeId;
+  if (spvOpcodeGeneratesType(op) || spvOpcodeIsConstant(op))
+    return kLayoutTypes;
+
+  switch (op) {
+    case SpvOpCapability:
+      return kLayoutCapabilities;
+    case SpvOpExtension:
+      return kLayoutExtensions;
+    case SpvOpExtInstImport:
+      return kLayoutExtInstImport;
+    case SpvOpMemoryModel:
+      return kLayoutMemoryModel;
+    case SpvOpEntryPoint:
+      return kLayoutEntryPoint;
+    case SpvOpExecutionMode:
+    case SpvOpExecutionModeId:
+      return kLayoutExecutionMode;
+    case SpvOpSourceContinued:
+    case SpvOpSource:
+    case SpvOpSourceExtension:
+    case SpvOpString:
+      return kLayoutDebug1;
+    case SpvOpName:
+    case SpvOpMemberName:
+      return kLayoutDebug2;
+    case SpvOpModuleProcessed:
+      return kLayoutDebug3;
+    case SpvOpDecorate:
+    case SpvOpMemberDecorate:
+    case SpvOpGroupDecorate:
+    case SpvOpGroupMemberDecorate:
+    case SpvOpDecorationGroup:
+    case SpvOpDecorateId:
+    case SpvOpDecorateStringGOOGLE:
+    case SpvOpMemberDecorateStringGOOGLE:
+      return kLayoutAnnotations;
+    case SpvOpTypeForwardPointer:
+      return kLayoutTypes;
+    case SpvOpVariable:
+      if (current_section == kLayoutTypes) return kLayoutTypes;
+      return kLayoutFunctionDefinitions;
+    case SpvOpExtInst:
+      // SpvOpExtInst is only allowed in types section for certain extended
+      // instruction sets. This will be checked separately.
+      if (current_section == kLayoutTypes) return kLayoutTypes;
+      return kLayoutFunctionDefinitions;
+    case SpvOpLine:
+    case SpvOpNoLine:
+    case SpvOpUndef:
+      if (current_section == kLayoutTypes) return kLayoutTypes;
+      return kLayoutFunctionDefinitions;
+    case SpvOpFunction:
+    case SpvOpFunctionParameter:
+    case SpvOpFunctionEnd:
+      if (current_section == kLayoutFunctionDeclarations)
+        return kLayoutFunctionDeclarations;
+      return kLayoutFunctionDefinitions;
+    default:
       break;
-    case kLayoutDebug1:
-      switch (op) {
-        case SpvOpSourceContinued:
-        case SpvOpSource:
-        case SpvOpSourceExtension:
-        case SpvOpString:
-          out = true;
-          break;
-        default: break;
-      }
-      break;
-    case kLayoutDebug2:
-      switch (op) {
-        case SpvOpName:
-        case SpvOpMemberName:
-          out = true;
-          break;
-        default: break;
-      }
-      break;
-    case kLayoutDebug3:
-      // Only OpModuleProcessed is allowed here.
-      out = (op == SpvOpModuleProcessed);
-      break;
-    case kLayoutAnnotations:
-      switch (op) {
-        case SpvOpDecorate:
-        case SpvOpMemberDecorate:
-        case SpvOpGroupDecorate:
-        case SpvOpGroupMemberDecorate:
-        case SpvOpDecorationGroup:
-        case SpvOpDecorateId:
-        case SpvOpDecorateStringGOOGLE:
-        case SpvOpMemberDecorateStringGOOGLE:
-          out = true;
-          break;
-        default: break;
-      }
-      break;
-    case kLayoutTypes:
-      if (spvOpcodeGeneratesType(op) || spvOpcodeIsConstant(op)) {
-        out = true;
-        break;
-      }
-      switch (op) {
-        case SpvOpTypeForwardPointer:
-        case SpvOpVariable:
-        case SpvOpLine:
-        case SpvOpNoLine:
-        case SpvOpUndef:
-        // SpvOpExtInst is only allowed here for certain extended instruction
-        // sets. This will be checked separately
-        case SpvOpExtInst:
-          out = true;
-          break;
-        default: break;
-      }
-      break;
-    case kLayoutFunctionDeclarations:
-    case kLayoutFunctionDefinitions:
-      // NOTE: These instructions should NOT be in these layout sections
-      if (spvOpcodeGeneratesType(op) || spvOpcodeIsConstant(op)) {
-        out = false;
-        break;
-      }
-      switch (op) {
-        case SpvOpCapability:
-        case SpvOpExtension:
-        case SpvOpExtInstImport:
-        case SpvOpMemoryModel:
-        case SpvOpEntryPoint:
-        case SpvOpExecutionMode:
-        case SpvOpExecutionModeId:
-        case SpvOpSourceContinued:
-        case SpvOpSource:
-        case SpvOpSourceExtension:
-        case SpvOpString:
-        case SpvOpName:
-        case SpvOpMemberName:
-        case SpvOpModuleProcessed:
-        case SpvOpDecorate:
-        case SpvOpMemberDecorate:
-        case SpvOpGroupDecorate:
-        case SpvOpGroupMemberDecorate:
-        case SpvOpDecorationGroup:
-        case SpvOpTypeForwardPointer:
-          out = false;
-          break;
-      default:
-        out = true;
-        break;
-      }
   }
-  // clang-format on
-  return out;
+  return kLayoutFunctionDefinitions;
+}
+
+bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) {
+  return layout == InstructionLayoutSection(layout, op);
 }
 
 // Counts the number of instructions and functions in the file.
@@ -311,6 +271,12 @@
   }
 }
 
+bool ValidationState_t::IsOpcodeInPreviousLayoutSection(SpvOp op) {
+  ModuleLayoutSection section =
+      InstructionLayoutSection(current_layout_section_, op);
+  return section < current_layout_section_;
+}
+
 bool ValidationState_t::IsOpcodeInCurrentLayoutSection(SpvOp op) {
   return IsInstructionInLayoutSection(current_layout_section_, op);
 }
@@ -1319,5 +1285,209 @@
   return true;
 }
 
+#define VUID_WRAP(vuid) "[" #vuid "] "
+
+// Currently no 2 VUID share the same id, so no need for |reference|
+std::string ValidationState_t::VkErrorID(uint32_t id,
+                                         const char* /*reference*/) const {
+  if (!spvIsVulkanEnv(context_->target_env)) {
+    return "";
+  }
+
+  // This large switch case is only searched when an error has occured.
+  // If an id is changed, the old case must be modified or removed. Each string
+  // here is interpreted as being "implemented"
+
+  // Clang format adds spaces between hyphens
+  // clang-format off
+  switch (id) {
+    case 4181:
+      return VUID_WRAP(VUID-BaseInstance-BaseInstance-04181);
+    case 4182:
+      return VUID_WRAP(VUID-BaseInstance-BaseInstance-04182);
+    case 4183:
+      return VUID_WRAP(VUID-BaseInstance-BaseInstance-04183);
+    case 4184:
+      return VUID_WRAP(VUID-BaseVertex-BaseVertex-04184);
+    case 4185:
+      return VUID_WRAP(VUID-BaseVertex-BaseVertex-04185);
+    case 4186:
+      return VUID_WRAP(VUID-BaseVertex-BaseVertex-04186);
+    case 4187:
+      return VUID_WRAP(VUID-ClipDistance-ClipDistance-04187);
+    case 4191:
+      return VUID_WRAP(VUID-ClipDistance-ClipDistance-04191);
+    case 4196:
+      return VUID_WRAP(VUID-CullDistance-CullDistance-04196);
+    case 4200:
+      return VUID_WRAP(VUID-CullDistance-CullDistance-04200);
+    case 4205:
+      return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04205);
+    case 4206:
+      return VUID_WRAP(VUID-DeviceIndex-DeviceIndex-04206);
+    case 4207:
+      return VUID_WRAP(VUID-DrawIndex-DrawIndex-04207);
+    case 4208:
+      return VUID_WRAP(VUID-DrawIndex-DrawIndex-04208);
+    case 4209:
+      return VUID_WRAP(VUID-DrawIndex-DrawIndex-04209);
+    case 4210:
+      return VUID_WRAP(VUID-FragCoord-FragCoord-04210);
+    case 4211:
+      return VUID_WRAP(VUID-FragCoord-FragCoord-04211);
+    case 4212:
+      return VUID_WRAP(VUID-FragCoord-FragCoord-04212);
+    case 4213:
+      return VUID_WRAP(VUID-FragDepth-FragDepth-04213);
+    case 4214:
+      return VUID_WRAP(VUID-FragDepth-FragDepth-04214);
+    case 4215:
+      return VUID_WRAP(VUID-FragDepth-FragDepth-04215);
+    case 4216:
+      return VUID_WRAP(VUID-FragDepth-FragDepth-04216);
+    case 4229:
+      return VUID_WRAP(VUID-FrontFacing-FrontFacing-04229);
+    case 4230:
+      return VUID_WRAP(VUID-FrontFacing-FrontFacing-04230);
+    case 4231:
+      return VUID_WRAP(VUID-FrontFacing-FrontFacing-04231);
+    case 4236:
+      return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04236);
+    case 4237:
+      return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04237);
+    case 4238:
+      return VUID_WRAP(VUID-GlobalInvocationId-GlobalInvocationId-04238);
+    case 4239:
+      return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04239);
+    case 4240:
+      return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04240);
+    case 4241:
+      return VUID_WRAP(VUID-HelperInvocation-HelperInvocation-04241);
+    case 4257:
+      return VUID_WRAP(VUID-InvocationId-InvocationId-04257);
+    case 4258:
+      return VUID_WRAP(VUID-InvocationId-InvocationId-04258);
+    case 4259:
+      return VUID_WRAP(VUID-InvocationId-InvocationId-04259);
+    case 4263:
+      return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04263);
+    case 4264:
+      return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04264);
+    case 4265:
+      return VUID_WRAP(VUID-InstanceIndex-InstanceIndex-04265);
+    case 4272:
+      return VUID_WRAP(VUID-Layer-Layer-04272);
+    case 4276:
+      return VUID_WRAP(VUID-Layer-Layer-04276);
+    case 4281:
+      return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04281);
+    case 4282:
+      return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04282);
+    case 4283:
+      return VUID_WRAP(VUID-LocalInvocationId-LocalInvocationId-04283);
+    case 4296:
+      return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04296);
+    case 4297:
+      return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04297);
+    case 4298:
+      return VUID_WRAP(VUID-NumWorkgroups-NumWorkgroups-04298);
+    case 4308:
+      return VUID_WRAP(VUID-PatchVertices-PatchVertices-04308);
+    case 4309:
+      return VUID_WRAP(VUID-PatchVertices-PatchVertices-04309);
+    case 4310:
+      return VUID_WRAP(VUID-PatchVertices-PatchVertices-04310);
+    case 4311:
+      return VUID_WRAP(VUID-PointCoord-PointCoord-04311);
+    case 4312:
+      return VUID_WRAP(VUID-PointCoord-PointCoord-04312);
+    case 4313:
+      return VUID_WRAP(VUID-PointCoord-PointCoord-04313);
+    case 4314:
+      return VUID_WRAP(VUID-PointSize-PointSize-04314);
+    case 4315:
+      return VUID_WRAP(VUID-PointSize-PointSize-04315);
+    case 4316:
+      return VUID_WRAP(VUID-PointSize-PointSize-04316);
+    case 4317:
+      return VUID_WRAP(VUID-PointSize-PointSize-04317);
+    case 4318:
+      return VUID_WRAP(VUID-Position-Position-04318);
+    case 4320:
+      return VUID_WRAP(VUID-Position-Position-04320);
+    case 4321:
+      return VUID_WRAP(VUID-Position-Position-04321);
+    case 4330:
+      return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04330);
+    case 4334:
+      return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04334);
+    case 4337:
+      return VUID_WRAP(VUID-PrimitiveId-PrimitiveId-04337);
+    case 4354:
+      return VUID_WRAP(VUID-SampleId-SampleId-04354);
+    case 4355:
+      return VUID_WRAP(VUID-SampleId-SampleId-04355);
+    case 4356:
+      return VUID_WRAP(VUID-SampleId-SampleId-04356);
+    case 4357:
+      return VUID_WRAP(VUID-SampleMask-SampleMask-04357);
+    case 4358:
+      return VUID_WRAP(VUID-SampleMask-SampleMask-04358);
+    case 4359:
+      return VUID_WRAP(VUID-SampleMask-SampleMask-04359);
+    case 4360:
+      return VUID_WRAP(VUID-SamplePosition-SamplePosition-04360);
+    case 4361:
+      return VUID_WRAP(VUID-SamplePosition-SamplePosition-04361);
+    case 4362:
+      return VUID_WRAP(VUID-SamplePosition-SamplePosition-04362);
+    case 4387:
+      return VUID_WRAP(VUID-TessCoord-TessCoord-04387);
+    case 4388:
+      return VUID_WRAP(VUID-TessCoord-TessCoord-04388);
+    case 4389:
+      return VUID_WRAP(VUID-TessCoord-TessCoord-04389);
+    case 4390:
+      return VUID_WRAP(VUID-TessLevelOuter-TessLevelOuter-04390);
+    case 4393:
+      return VUID_WRAP(VUID-TessLevelOuter-TessLevelOuter-04393);
+    case 4394:
+      return VUID_WRAP(VUID-TessLevelInner-TessLevelInner-04394);
+    case 4397:
+      return VUID_WRAP(VUID-TessLevelInner-TessLevelInner-04397);
+    case 4398:
+      return VUID_WRAP(VUID-VertexIndex-VertexIndex-04398);
+    case 4399:
+      return VUID_WRAP(VUID-VertexIndex-VertexIndex-04399);
+    case 4400:
+      return VUID_WRAP(VUID-VertexIndex-VertexIndex-04400);
+    case 4401:
+      return VUID_WRAP(VUID-ViewIndex-ViewIndex-04401);
+    case 4402:
+      return VUID_WRAP(VUID-ViewIndex-ViewIndex-04402);
+    case 4403:
+      return VUID_WRAP(VUID-ViewIndex-ViewIndex-04403);
+    case 4404:
+      return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04404);
+    case 4408:
+      return VUID_WRAP(VUID-ViewportIndex-ViewportIndex-04408);
+    case 4422:
+      return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04422);
+    case 4423:
+      return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04423);
+    case 4424:
+      return VUID_WRAP(VUID-WorkgroupId-WorkgroupId-04424);
+    case 4425:
+      return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04425);
+    case 4426:
+      return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04426);
+    case 4427:
+      return VUID_WRAP(VUID-WorkgroupSize-WorkgroupSize-04427);
+    default:
+      return "";  // unknown id
+  };
+  // clang-format on
+}
+
 }  // namespace val
 }  // namespace spvtools
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index e5d31ac..aeb1ca8 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -195,6 +195,9 @@
   /// Increments the module_layout_order_section_
   void ProgressToNextLayoutSectionOrder();
 
+  /// Determines if the op instruction is in a previous layout section
+  bool IsOpcodeInPreviousLayoutSection(SpvOp op);
+
   /// Determines if the op instruction is part of the current section
   bool IsOpcodeInCurrentLayoutSection(SpvOp op);
 
@@ -707,6 +710,22 @@
   // Validates the storage class for the target environment.
   bool IsValidStorageClass(SpvStorageClass storage_class) const;
 
+  // Takes a Vulkan Valid Usage ID (VUID) as |id| and optional |reference| and
+  // will return a non-empty string only if ID is known and targeting Vulkan.
+  // VUIDs are found in the Vulkan-Docs repo in the form "[[VUID-ref-ref-id]]"
+  // where "id" is always an 5 char long number (with zeros padding) and matches
+  // to |id|. |reference| is used if there is a "common validity" and the VUID
+  // shares the same |id| value.
+  //
+  // More details about Vulkan validation can be found in Vulkan Guide:
+  // https://github.com/KhronosGroup/Vulkan-Guide/blob/master/chapters/validation_overview.md
+  std::string VkErrorID(uint32_t id, const char* reference = nullptr) const;
+
+  // Testing method to allow setting the current layout section.
+  void SetCurrentLayoutSectionForTesting(ModuleLayoutSection section) {
+    current_layout_section_ = section;
+  }
+
  private:
   ValidationState_t(const ValidationState_t&);
 
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 70999f9..8ede58b 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -159,12 +159,12 @@
 add_spvtools_unittest(
   TARGET spirv_unit_tests
   SRCS ${TEST_SOURCES}
-  LIBS ${SPIRV_TOOLS})
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
 
 add_spvtools_unittest(
   TARGET c_interface
   SRCS c_interface_test.cpp
-  LIBS ${SPIRV_TOOLS})
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
 
 add_spvtools_unittest(
   TARGET c_interface_shared
@@ -181,7 +181,7 @@
 add_spvtools_unittest(
   TARGET timer
   SRCS timer_test.cpp
-  LIBS ${SPIRV_TOOLS})
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
 endif()
 
 
diff --git a/test/binary_header_get_test.cpp b/test/binary_header_get_test.cpp
index f8f6bdb..3ce0b63 100644
--- a/test/binary_header_get_test.cpp
+++ b/test/binary_header_get_test.cpp
@@ -81,5 +81,37 @@
   }
 }
 
+TEST_F(BinaryHeaderGet, VersionNonZeroHighByte) {
+  spv_header_t header;
+  code[1] = 0xFF010300;
+  spv_const_binary_t const_bin = get_const_binary();
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header));
+}
+
+TEST_F(BinaryHeaderGet, VersionNonZeroLowByte) {
+  spv_header_t header;
+  code[1] = 0x000103F0;
+  spv_const_binary_t const_bin = get_const_binary();
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header));
+}
+
+TEST_F(BinaryHeaderGet, VersionTooLow) {
+  spv_header_t header;
+  code[1] = 0x00000300;
+  spv_const_binary_t const_bin = get_const_binary();
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header));
+}
+
+TEST_F(BinaryHeaderGet, VersionTooHigh) {
+  spv_header_t header;
+  code[1] = 0x000F0300;
+  spv_const_binary_t const_bin = get_const_binary();
+  ASSERT_EQ(SPV_ERROR_INVALID_BINARY,
+            spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, &header));
+}
+
 }  // namespace
 }  // namespace spvtools
diff --git a/test/binary_parse_test.cpp b/test/binary_parse_test.cpp
index 54664fc..93e87bd 100644
--- a/test/binary_parse_test.cpp
+++ b/test/binary_parse_test.cpp
@@ -92,7 +92,7 @@
   return os;
 }
 
-// Sanity check for the equality operator on ParsedInstruction.
+// Basic check for the equality operator on ParsedInstruction.
 TEST(ParsedInstruction, ZeroInitializedAreEqual) {
   spv_parsed_instruction_t pi = {};
   ParsedInstruction a(pi);
diff --git a/test/enum_string_mapping_test.cpp b/test/enum_string_mapping_test.cpp
index 184ae4f..9bbd8ca 100644
--- a/test/enum_string_mapping_test.cpp
+++ b/test/enum_string_mapping_test.cpp
@@ -1,4 +1,6 @@
 // Copyright (c) 2017 Google Inc.
+// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
+// reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -180,6 +182,7 @@
          {SpvCapabilityAtomicFloat32AddEXT, "AtomicFloat32AddEXT"},
          {SpvCapabilityAtomicFloat64AddEXT, "AtomicFloat64AddEXT"},
          {SpvCapabilityMultiView, "MultiView"},
+         {SpvCapabilityInt64ImageEXT, "Int64ImageEXT"},
          {SpvCapabilitySampleMaskOverrideCoverageNV,
           "SampleMaskOverrideCoverageNV"},
          {SpvCapabilityGeometryShaderPassthroughNV,
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 7047749..2e93293 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -17,15 +17,25 @@
   set(SOURCES
           fuzz_test_util.h
 
+          call_graph_test.cpp
+          comparator_deep_blocks_first_test.cpp
           data_synonym_transformation_test.cpp
           equivalence_relation_test.cpp
-          fact_manager_test.cpp
+          fact_manager/constant_uniform_facts_test.cpp
+          fact_manager/data_synonym_and_id_equation_facts_test.cpp
+          fact_manager/dead_block_facts_test.cpp
+          fact_manager/irrelevant_value_facts_test.cpp
           fuzz_test_util.cpp
+          fuzzer_pass_add_opphi_synonyms_test.cpp
           fuzzer_pass_construct_composites_test.cpp
           fuzzer_pass_donate_modules_test.cpp
+          fuzzer_pass_outline_functions_test.cpp
           instruction_descriptor_test.cpp
+          fuzzer_pass_test.cpp
           replayer_test.cpp
+          shrinker_test.cpp
           transformation_access_chain_test.cpp
+          transformation_add_bit_instruction_synonym_test.cpp
           transformation_add_constant_boolean_test.cpp
           transformation_add_constant_composite_test.cpp
           transformation_add_constant_null_test.cpp
@@ -34,12 +44,16 @@
           transformation_add_dead_block_test.cpp
           transformation_add_dead_break_test.cpp
           transformation_add_dead_continue_test.cpp
+          transformation_add_early_terminator_wrapper_test.cpp
           transformation_add_function_test.cpp
           transformation_add_global_undef_test.cpp
           transformation_add_global_variable_test.cpp
           transformation_add_image_sample_unused_components_test.cpp
           transformation_add_local_variable_test.cpp
+          transformation_add_loop_preheader_test.cpp
+          transformation_add_loop_to_create_int_constant_synonym_test.cpp
           transformation_add_no_contraction_decoration_test.cpp
+          transformation_add_opphi_synonym_test.cpp
           transformation_add_parameter_test.cpp
           transformation_add_relaxed_decoration_test.cpp
           transformation_add_synonym_test.cpp
@@ -55,25 +69,41 @@
           transformation_adjust_branch_weights_test.cpp
           transformation_composite_construct_test.cpp
           transformation_composite_extract_test.cpp
+          transformation_composite_insert_test.cpp
           transformation_compute_data_synonym_fact_closure_test.cpp
+          transformation_duplicate_region_with_selection_test.cpp
           transformation_equation_instruction_test.cpp
+          transformation_expand_vector_reduction_test.cpp
+          transformation_flatten_conditional_branch_test.cpp
           transformation_function_call_test.cpp
+          transformation_inline_function_test.cpp
           transformation_invert_comparison_operator_test.cpp
           transformation_load_test.cpp
+          transformation_make_vector_operation_dynamic_test.cpp
           transformation_merge_blocks_test.cpp
+          transformation_merge_function_returns_test.cpp
           transformation_move_block_down_test.cpp
+          transformation_move_instruction_down_test.cpp
+          transformation_mutate_pointer_test.cpp
           transformation_outline_function_test.cpp
           transformation_permute_function_parameters_test.cpp
           transformation_permute_phi_operands_test.cpp
+          transformation_propagate_instruction_down_test.cpp
+          transformation_propagate_instruction_up_test.cpp
           transformation_push_id_through_variable_test.cpp
-          transformation_replace_parameter_with_global_test.cpp
+          transformation_replace_add_sub_mul_with_carrying_extended_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
+          transformation_replace_branch_from_dead_block_with_exit_test.cpp
           transformation_replace_copy_object_with_store_load_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
           transformation_replace_copy_memory_with_load_store_test.cpp
           transformation_replace_id_with_synonym_test.cpp
+          transformation_replace_irrelevant_id_test.cpp
           transformation_replace_linear_algebra_instruction_test.cpp
           transformation_replace_load_store_with_copy_memory_test.cpp
+          transformation_replace_opphi_id_from_dead_predecessor_test.cpp
+          transformation_replace_opselect_with_conditional_branch_test.cpp
+          transformation_replace_parameter_with_global_test.cpp
           transformation_replace_params_with_struct_test.cpp
           transformation_set_function_control_test.cpp
           transformation_set_loop_control_test.cpp
@@ -86,6 +116,8 @@
           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
           uniform_buffer_element_descriptor_test.cpp)
 
   if (${SPIRV_ENABLE_LONG_FUZZER_TESTS})
diff --git a/test/fuzz/call_graph_test.cpp b/test/fuzz/call_graph_test.cpp
new file mode 100644
index 0000000..c9bd221
--- /dev/null
+++ b/test/fuzz/call_graph_test.cpp
@@ -0,0 +1,378 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/call_graph.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+// The SPIR-V came from this GLSL, slightly modified
+// (main is %2, A is %35, B is %48, C is %50, D is %61):
+//
+//    #version 310 es
+//
+//    int A (int x) {
+//      return x + 1;
+//    }
+//
+//    void D() {
+//    }
+//
+//    void C() {
+//      int x = 0;
+//      int y = 0;
+//
+//      while (x < 10) {
+//        while (y < 10) {
+//          y = A(y);
+//        }
+//        x = A(x);
+//      }
+//    }
+//
+//    void B () {
+//      int x = 0;
+//      int y = 0;
+//
+//      while (x < 10) {
+//        D();
+//        while (y < 10) {
+//          y = A(y);
+//          C();
+//        }
+//        x++;
+//      }
+//
+//    }
+//
+//    void main()
+//    {
+//      int x = 0;
+//      int y = 0;
+//      int z = 0;
+//
+//      while (x < 10) {
+//        while(y < 10) {
+//          y = A(x);
+//          while (z < 10) {
+//            z = A(z);
+//          }
+//        }
+//        x += 2;
+//      }
+//
+//      B();
+//      C();
+//    }
+std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypePointer Function %5
+          %7 = OpTypeFunction %5 %6
+          %8 = OpConstant %5 1
+          %9 = OpConstant %5 0
+         %10 = OpConstant %5 10
+         %11 = OpTypeBool
+         %12 = OpConstant %5 2
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+         %14 = OpVariable %6 Function
+         %15 = OpVariable %6 Function
+         %16 = OpVariable %6 Function
+         %17 = OpVariable %6 Function
+         %18 = OpVariable %6 Function
+               OpStore %14 %9
+               OpStore %15 %9
+               OpStore %16 %9
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpLoad %5 %14
+         %24 = OpSLessThan %11 %23 %10
+               OpBranchConditional %24 %25 %20
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpLoopMerge %27 %28 None
+               OpBranch %29
+         %29 = OpLabel
+         %30 = OpLoad %5 %15
+         %31 = OpSLessThan %11 %30 %10
+               OpBranchConditional %31 %32 %27
+         %32 = OpLabel
+         %33 = OpLoad %5 %14
+               OpStore %17 %33
+         %34 = OpFunctionCall %5 %35 %17
+               OpStore %15 %34
+               OpBranch %36
+         %36 = OpLabel
+               OpLoopMerge %37 %38 None
+               OpBranch %39
+         %39 = OpLabel
+         %40 = OpLoad %5 %16
+         %41 = OpSLessThan %11 %40 %10
+               OpBranchConditional %41 %42 %37
+         %42 = OpLabel
+         %43 = OpLoad %5 %16
+               OpStore %18 %43
+         %44 = OpFunctionCall %5 %35 %18
+               OpStore %16 %44
+               OpBranch %38
+         %38 = OpLabel
+               OpBranch %36
+         %37 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %26
+         %27 = OpLabel
+         %45 = OpLoad %5 %14
+         %46 = OpIAdd %5 %45 %12
+               OpStore %14 %46
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %19
+         %20 = OpLabel
+         %47 = OpFunctionCall %3 %48
+         %49 = OpFunctionCall %3 %50
+               OpReturn
+               OpFunctionEnd
+         %35 = OpFunction %5 None %7
+         %51 = OpFunctionParameter %6
+         %52 = OpLabel
+         %53 = OpLoad %5 %51
+         %54 = OpIAdd %5 %53 %8
+               OpReturnValue %54
+               OpFunctionEnd
+         %48 = OpFunction %3 None %4
+         %55 = OpLabel
+         %56 = OpVariable %6 Function
+         %57 = OpVariable %6 Function
+         %58 = OpVariable %6 Function
+               OpStore %56 %9
+               OpStore %57 %9
+               OpBranch %59
+         %59 = OpLabel
+         %60 = OpFunctionCall %3 %61
+               OpLoopMerge %62 %63 None
+               OpBranch %64
+         %64 = OpLabel
+               OpLoopMerge %65 %66 None
+               OpBranch %67
+         %67 = OpLabel
+         %68 = OpLoad %5 %57
+         %69 = OpSLessThan %11 %68 %10
+               OpBranchConditional %69 %70 %65
+         %70 = OpLabel
+         %71 = OpLoad %5 %57
+               OpStore %58 %71
+         %72 = OpFunctionCall %5 %35 %58
+               OpStore %57 %72
+         %73 = OpFunctionCall %3 %50
+               OpBranch %66
+         %66 = OpLabel
+               OpBranch %64
+         %65 = OpLabel
+         %74 = OpLoad %5 %56
+         %75 = OpIAdd %5 %74 %8
+               OpStore %56 %75
+               OpBranch %63
+         %63 = OpLabel
+         %76 = OpLoad %5 %56
+         %77 = OpSLessThan %11 %76 %10
+               OpBranchConditional %77 %59 %62
+         %62 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %50 = OpFunction %3 None %4
+         %78 = OpLabel
+         %79 = OpVariable %6 Function
+         %80 = OpVariable %6 Function
+         %81 = OpVariable %6 Function
+         %82 = OpVariable %6 Function
+               OpStore %79 %9
+               OpStore %80 %9
+               OpBranch %83
+         %83 = OpLabel
+               OpLoopMerge %84 %85 None
+               OpBranch %86
+         %86 = OpLabel
+         %87 = OpLoad %5 %79
+         %88 = OpSLessThan %11 %87 %10
+               OpBranchConditional %88 %89 %84
+         %89 = OpLabel
+               OpBranch %90
+         %90 = OpLabel
+               OpLoopMerge %91 %92 None
+               OpBranch %93
+         %93 = OpLabel
+         %94 = OpLoad %5 %80
+         %95 = OpSLessThan %11 %94 %10
+               OpBranchConditional %95 %96 %91
+         %96 = OpLabel
+         %97 = OpLoad %5 %80
+               OpStore %81 %97
+         %98 = OpFunctionCall %5 %35 %81
+               OpStore %80 %98
+               OpBranch %92
+         %92 = OpLabel
+               OpBranch %90
+         %91 = OpLabel
+         %99 = OpLoad %5 %79
+               OpStore %82 %99
+        %100 = OpFunctionCall %5 %35 %82
+               OpStore %79 %100
+               OpBranch %85
+         %85 = OpLabel
+               OpBranch %83
+         %84 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %61 = OpFunction %3 None %4
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+// We have that:
+// main calls:
+// - A (maximum loop nesting depth of function call: 3)
+// - B (0)
+// - C (0)
+// A calls nothing.
+// B calls:
+// - A (2)
+// - C (2)
+// - D (1)
+// C calls:
+// - A (2)
+// D calls nothing.
+
+TEST(CallGraphTest, FunctionInDegree) {
+  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));
+
+  const auto graph = CallGraph(context.get());
+
+  const auto& function_in_degree = graph.GetFunctionInDegree();
+  // Check the in-degrees of, in order: main, A, B, C, D.
+  ASSERT_EQ(function_in_degree.at(2), 0);
+  ASSERT_EQ(function_in_degree.at(35), 3);
+  ASSERT_EQ(function_in_degree.at(48), 1);
+  ASSERT_EQ(function_in_degree.at(50), 2);
+  ASSERT_EQ(function_in_degree.at(61), 1);
+}
+
+TEST(CallGraphTest, DirectCallees) {
+  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));
+
+  const auto graph = CallGraph(context.get());
+
+  // Check the callee sets of, in order: main, A, B, C, D.
+  ASSERT_EQ(graph.GetDirectCallees(2), std::set<uint32_t>({35, 48, 50}));
+  ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({}));
+  ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61}));
+  ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35}));
+  ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({}));
+}
+
+TEST(CallGraphTest, IndirectCallees) {
+  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));
+
+  const auto graph = CallGraph(context.get());
+
+  // Check the callee sets of, in order: main, A, B, C, D.
+  ASSERT_EQ(graph.GetIndirectCallees(2), std::set<uint32_t>({35, 48, 50, 61}));
+  ASSERT_EQ(graph.GetDirectCallees(35), std::set<uint32_t>({}));
+  ASSERT_EQ(graph.GetDirectCallees(48), std::set<uint32_t>({35, 50, 61}));
+  ASSERT_EQ(graph.GetDirectCallees(50), std::set<uint32_t>({35}));
+  ASSERT_EQ(graph.GetDirectCallees(61), std::set<uint32_t>({}));
+}
+
+TEST(CallGraphTest, TopologicalOrder) {
+  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));
+
+  const auto graph = CallGraph(context.get());
+
+  const auto& topological_ordering = graph.GetFunctionsInTopologicalOrder();
+
+  // The possible topological orderings are:
+  //  - main, B, D, C, A
+  //  - main, B, C, D, A
+  //  - main, B, C, A, D
+  ASSERT_TRUE(
+      topological_ordering == std::vector<uint32_t>({2, 48, 61, 50, 35}) ||
+      topological_ordering == std::vector<uint32_t>({2, 48, 50, 61, 35}) ||
+      topological_ordering == std::vector<uint32_t>({2, 48, 50, 35, 61}));
+}
+
+TEST(CallGraphTest, LoopNestingDepth) {
+  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));
+
+  const auto graph = CallGraph(context.get());
+
+  // Check the maximum loop nesting depth for function calls to, in order:
+  // main, A, B, C, D
+  ASSERT_EQ(graph.GetMaxCallNestingDepth(2), 0);
+  ASSERT_EQ(graph.GetMaxCallNestingDepth(35), 4);
+  ASSERT_EQ(graph.GetMaxCallNestingDepth(48), 0);
+  ASSERT_EQ(graph.GetMaxCallNestingDepth(50), 2);
+  ASSERT_EQ(graph.GetMaxCallNestingDepth(61), 1);
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/comparator_deep_blocks_first_test.cpp b/test/fuzz/comparator_deep_blocks_first_test.cpp
new file mode 100644
index 0000000..d7dbde5
--- /dev/null
+++ b/test/fuzz/comparator_deep_blocks_first_test.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/comparator_deep_blocks_first.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fact_manager/fact_manager.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/transformation_context.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypePointer Function %7
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 10
+         %11 = OpConstant %7 2
+          %2 = OpFunction %3 None %4
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %6 %14 %15
+         %14 = OpLabel
+               OpBranch %13
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %17 %18 None
+               OpBranch %19
+         %19 = OpLabel
+               OpBranchConditional %6 %20 %17
+         %20 = OpLabel
+               OpSelectionMerge %21 None
+               OpBranchConditional %6 %22 %23
+         %22 = OpLabel
+               OpBranch %21
+         %23 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(ComparatorDeepBlocksFirstTest, Compare) {
+  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);
+  auto is_deeper = ComparatorDeepBlocksFirst(context.get());
+
+  // The block ids and the corresponding depths are:
+  // 12, 13          -> depth 0
+  // 14, 15, 16, 17  -> depth 1
+  // 18, 19, 20, 21  -> depth 2
+  // 22, 23          -> depth 3
+
+  // Perform some comparisons and check that they return true iff the first
+  // block is deeper than the second.
+  ASSERT_FALSE(is_deeper(12, 12));
+  ASSERT_FALSE(is_deeper(12, 13));
+  ASSERT_FALSE(is_deeper(12, 14));
+  ASSERT_FALSE(is_deeper(12, 18));
+  ASSERT_FALSE(is_deeper(12, 22));
+  ASSERT_TRUE(is_deeper(14, 12));
+  ASSERT_FALSE(is_deeper(14, 15));
+  ASSERT_FALSE(is_deeper(15, 14));
+  ASSERT_FALSE(is_deeper(14, 18));
+  ASSERT_TRUE(is_deeper(18, 12));
+  ASSERT_TRUE(is_deeper(18, 16));
+}
+
+TEST(ComparatorDeepBlocksFirstTest, Sort) {
+  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);
+  // Check that, sorting using the comparator, the blocks are ordered from more
+  // deeply nested to less deeply nested.
+  // 17 has depth 1, 20 has depth 2, 13 has depth 0.
+  std::vector<opt::BasicBlock*> blocks = {context->get_instr_block(17),
+                                          context->get_instr_block(20),
+                                          context->get_instr_block(13)};
+
+  std::sort(blocks.begin(), blocks.end(),
+            ComparatorDeepBlocksFirst(context.get()));
+
+  // Check that the blocks are in the correct order.
+  ASSERT_EQ(blocks[0]->id(), 20);
+  ASSERT_EQ(blocks[1]->id(), 17);
+  ASSERT_EQ(blocks[2]->id(), 13);
+}
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/data_synonym_transformation_test.cpp b/test/fuzz/data_synonym_transformation_test.cpp
index 66ce769..7e93f29 100644
--- a/test/fuzz/data_synonym_transformation_test.cpp
+++ b/test/fuzz/data_synonym_transformation_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/data_descriptor.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/id_use_descriptor.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/transformation_composite_extract.h"
@@ -28,14 +31,14 @@
 // number of transformations that relate to data synonyms.
 
 protobufs::Fact MakeSynonymFact(uint32_t first_id,
-                                std::vector<uint32_t>&& first_indices,
+                                const std::vector<uint32_t>& first_indices,
                                 uint32_t second_id,
-                                std::vector<uint32_t>&& second_indices) {
+                                const std::vector<uint32_t>& second_indices) {
   protobufs::FactDataSynonym data_synonym_fact;
   *data_synonym_fact.mutable_data1() =
-      MakeDataDescriptor(first_id, std::move(first_indices));
+      MakeDataDescriptor(first_id, first_indices);
   *data_synonym_fact.mutable_data2() =
-      MakeDataDescriptor(second_id, std::move(second_indices));
+      MakeDataDescriptor(second_id, second_indices);
   protobufs::Fact result;
   *result.mutable_data_synonym_fact() = data_synonym_fact;
   return result;
@@ -120,27 +123,26 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
 
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(12, {}, 100, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(13, {}, 100, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(22, {}, 100, {2}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(28, {}, 101, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(23, {}, 101, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(32, {}, 101, {2}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(23, {}, 101, {3}), context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(12, {}, 100, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(13, {}, 100, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(22, {}, 100, {2}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(28, {}, 101, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(23, {}, 101, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(32, {}, 101, {2}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(23, {}, 101, {3}));
 
   // Replace %12 with %100[0] in '%25 = OpAccessChain %24 %20 %12'
   auto instruction_descriptor_1 =
@@ -160,7 +162,8 @@
   ASSERT_TRUE(
       replacement_1.IsApplicable(context.get(), transformation_context));
   replacement_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %13 with %100[1] in 'OpStore %15 %13'
   auto instruction_descriptor_2 = MakeInstructionDescriptor(100, SpvOpStore, 0);
@@ -175,7 +178,8 @@
   ASSERT_TRUE(
       replacement_2.IsApplicable(context.get(), transformation_context));
   replacement_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %22 with %100[2] in '%23 = OpConvertSToF %16 %22'
   auto instruction_descriptor_3 =
@@ -195,7 +199,8 @@
   ASSERT_FALSE(
       bad_replacement_3.IsApplicable(context.get(), transformation_context));
   replacement_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %28 with %101[0] in 'OpStore %33 %28'
   auto instruction_descriptor_4 = MakeInstructionDescriptor(33, SpvOpStore, 0);
@@ -214,7 +219,8 @@
   ASSERT_TRUE(
       replacement_4.IsApplicable(context.get(), transformation_context));
   replacement_4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %23 with %101[1] in '%50 = OpCopyObject %16 %23'
   auto instruction_descriptor_5 =
@@ -234,7 +240,8 @@
   ASSERT_FALSE(
       bad_replacement_5.IsApplicable(context.get(), transformation_context));
   replacement_5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %32 with %101[2] in 'OpStore %33 %32'
   auto instruction_descriptor_6 = MakeInstructionDescriptor(33, SpvOpStore, 1);
@@ -253,7 +260,8 @@
   ASSERT_TRUE(
       replacement_6.IsApplicable(context.get(), transformation_context));
   replacement_6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %23 with %101[3] in '%51 = OpCopyObject %16 %23'
   auto instruction_descriptor_7 =
@@ -273,7 +281,8 @@
   ASSERT_FALSE(
       bad_replacement_7.IsApplicable(context.get(), transformation_context));
   replacement_7.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -408,19 +417,18 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
 
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(23, {}, 100, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(25, {}, 100, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(50, {}, 100, {2}), context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(23, {}, 100, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(25, {}, 100, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(50, {}, 100, {2}));
 
   // Replace %23 with %100[0] in '%26 = OpFAdd %7 %23 %25'
   auto instruction_descriptor_1 = MakeInstructionDescriptor(26, SpvOpFAdd, 0);
@@ -433,7 +441,8 @@
   ASSERT_TRUE(
       replacement_1.IsApplicable(context.get(), transformation_context));
   replacement_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %25 with %100[1] in '%26 = OpFAdd %7 %23 %25'
   auto instruction_descriptor_2 = MakeInstructionDescriptor(26, SpvOpFAdd, 0);
@@ -446,7 +455,8 @@
   ASSERT_TRUE(
       replacement_2.IsApplicable(context.get(), transformation_context));
   replacement_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -578,27 +588,26 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
 
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(16, {}, 100, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(45, {}, 100, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(27, {}, 101, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(36, {}, 101, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(27, {}, 101, {2}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(22, {}, 102, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(15, {}, 102, {1}), context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(16, {}, 100, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(45, {}, 100, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(27, {}, 101, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(36, {}, 101, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(27, {}, 101, {2}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(22, {}, 102, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(15, {}, 102, {1}));
 
   // Replace %45 with %100[1] in '%46 = OpCompositeConstruct %32 %35 %45'
   auto instruction_descriptor_1 =
@@ -612,7 +621,8 @@
   ASSERT_TRUE(
       replacement_1.IsApplicable(context.get(), transformation_context));
   replacement_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace second occurrence of %27 with %101[0] in '%28 =
   // OpCompositeConstruct %8 %27 %27'
@@ -627,7 +637,8 @@
   ASSERT_TRUE(
       replacement_2.IsApplicable(context.get(), transformation_context));
   replacement_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %36 with %101[1] in '%45 = OpCompositeConstruct %31 %36 %41 %44'
   auto instruction_descriptor_3 =
@@ -641,7 +652,8 @@
   ASSERT_TRUE(
       replacement_3.IsApplicable(context.get(), transformation_context));
   replacement_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace first occurrence of %27 with %101[2] in '%28 = OpCompositeConstruct
   // %8 %27 %27'
@@ -656,7 +668,8 @@
   ASSERT_TRUE(
       replacement_4.IsApplicable(context.get(), transformation_context));
   replacement_4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %22 with %102[0] in 'OpStore %23 %22'
   auto instruction_descriptor_5 = MakeInstructionDescriptor(23, SpvOpStore, 0);
@@ -669,7 +682,8 @@
   ASSERT_TRUE(
       replacement_5.IsApplicable(context.get(), transformation_context));
   replacement_5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -868,53 +882,52 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
 
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(20, {0}, 100, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(20, {1}, 100, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(20, {2}, 100, {2}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(54, {}, 100, {3}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(15, {0}, 101, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(15, {1}, 101, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(19, {0}, 101, {2}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(19, {1}, 101, {3}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(27, {}, 102, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(15, {0}, 102, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(15, {1}, 102, {2}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(33, {}, 103, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(47, {0}, 103, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(47, {1}, 103, {2}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(47, {2}, 103, {3}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(42, {}, 104, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(45, {}, 104, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(38, {0}, 105, {0}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(38, {1}, 105, {1}), context.get());
-  transformation_context.GetFactManager()->AddFact(
-      MakeSynonymFact(46, {}, 105, {2}), context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(20, {0}, 100, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(20, {1}, 100, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(20, {2}, 100, {2}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(54, {}, 100, {3}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(15, {0}, 101, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(15, {1}, 101, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(19, {0}, 101, {2}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(19, {1}, 101, {3}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(27, {}, 102, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(15, {0}, 102, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(15, {1}, 102, {2}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(33, {}, 103, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(47, {0}, 103, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(47, {1}, 103, {2}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(47, {2}, 103, {3}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(42, {}, 104, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(45, {}, 104, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(38, {0}, 105, {0}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(38, {1}, 105, {1}));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(46, {}, 105, {2}));
 
   // Replace %20 with %100[0:2] in '%80 = OpCopyObject %16 %20'
   auto instruction_descriptor_1 =
@@ -923,14 +936,15 @@
                                                100, 100, {0, 1, 2});
   ASSERT_TRUE(shuffle_1.IsApplicable(context.get(), transformation_context));
   shuffle_1.Apply(context.get(), &transformation_context);
-  fact_manager.ComputeClosureOfFacts(context.get(), 100);
+  transformation_context.GetFactManager()->ComputeClosureOfFacts(100);
 
   auto replacement_1 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(20, instruction_descriptor_1, 0), 200);
   ASSERT_TRUE(
       replacement_1.IsApplicable(context.get(), transformation_context));
   replacement_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %54 with %100[3] in '%56 = OpFOrdNotEqual %30 %54 %55'
   auto instruction_descriptor_2 =
@@ -945,7 +959,8 @@
   ASSERT_TRUE(
       replacement_2.IsApplicable(context.get(), transformation_context));
   replacement_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %15 with %101[0:1] in 'OpStore %12 %15'
   auto instruction_descriptor_3 = MakeInstructionDescriptor(64, SpvOpStore, 0);
@@ -953,14 +968,15 @@
                                                101, 101, {0, 1});
   ASSERT_TRUE(shuffle_3.IsApplicable(context.get(), transformation_context));
   shuffle_3.Apply(context.get(), &transformation_context);
-  fact_manager.ComputeClosureOfFacts(context.get(), 100);
+  transformation_context.GetFactManager()->ComputeClosureOfFacts(100);
 
   auto replacement_3 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(15, instruction_descriptor_3, 1), 202);
   ASSERT_TRUE(
       replacement_3.IsApplicable(context.get(), transformation_context));
   replacement_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %19 with %101[2:3] in '%81 = OpVectorShuffle %16 %19 %19 0 0 1'
   auto instruction_descriptor_4 =
@@ -969,14 +985,15 @@
                                                101, 101, {2, 3});
   ASSERT_TRUE(shuffle_4.IsApplicable(context.get(), transformation_context));
   shuffle_4.Apply(context.get(), &transformation_context);
-  fact_manager.ComputeClosureOfFacts(context.get(), 100);
+  transformation_context.GetFactManager()->ComputeClosureOfFacts(100);
 
   auto replacement_4 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(19, instruction_descriptor_4, 0), 203);
   ASSERT_TRUE(
       replacement_4.IsApplicable(context.get(), transformation_context));
   replacement_4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %27 with %102[0] in '%82 = OpCompositeConstruct %21 %26 %27 %28
   // %25'
@@ -992,7 +1009,8 @@
   ASSERT_TRUE(
       replacement_5.IsApplicable(context.get(), transformation_context));
   replacement_5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %15 with %102[1:2] in '%83 = OpCopyObject %10 %15'
   auto instruction_descriptor_6 =
@@ -1001,14 +1019,15 @@
                                                102, 102, {1, 2});
   ASSERT_TRUE(shuffle_6.IsApplicable(context.get(), transformation_context));
   shuffle_6.Apply(context.get(), &transformation_context);
-  fact_manager.ComputeClosureOfFacts(context.get(), 100);
+  transformation_context.GetFactManager()->ComputeClosureOfFacts(100);
 
   auto replacement_6 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(15, instruction_descriptor_6, 0), 205);
   ASSERT_TRUE(
       replacement_6.IsApplicable(context.get(), transformation_context));
   replacement_6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %33 with %103[0] in '%86 = OpCopyObject %30 %33'
   auto instruction_descriptor_7 =
@@ -1022,7 +1041,8 @@
   ASSERT_TRUE(
       replacement_7.IsApplicable(context.get(), transformation_context));
   replacement_7.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %47 with %103[1:3] in '%84 = OpCopyObject %39 %47'
   auto instruction_descriptor_8 =
@@ -1031,14 +1051,15 @@
                                                103, 103, {1, 2, 3});
   ASSERT_TRUE(shuffle_8.IsApplicable(context.get(), transformation_context));
   shuffle_8.Apply(context.get(), &transformation_context);
-  fact_manager.ComputeClosureOfFacts(context.get(), 100);
+  transformation_context.GetFactManager()->ComputeClosureOfFacts(100);
 
   auto replacement_8 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(47, instruction_descriptor_8, 0), 207);
   ASSERT_TRUE(
       replacement_8.IsApplicable(context.get(), transformation_context));
   replacement_8.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %42 with %104[0] in '%85 = OpCopyObject %30 %42'
   auto instruction_descriptor_9 =
@@ -1052,7 +1073,8 @@
   ASSERT_TRUE(
       replacement_9.IsApplicable(context.get(), transformation_context));
   replacement_9.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %45 with %104[1] in '%63 = OpLogicalOr %30 %45 %46'
   auto instruction_descriptor_10 =
@@ -1066,7 +1088,8 @@
   ASSERT_TRUE(
       replacement_10.IsApplicable(context.get(), transformation_context));
   replacement_10.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %38 with %105[0:1] in 'OpStore %36 %38'
   auto instruction_descriptor_11 = MakeInstructionDescriptor(85, SpvOpStore, 0);
@@ -1074,14 +1097,15 @@
                                                 105, 105, {0, 1});
   ASSERT_TRUE(shuffle_11.IsApplicable(context.get(), transformation_context));
   shuffle_11.Apply(context.get(), &transformation_context);
-  fact_manager.ComputeClosureOfFacts(context.get(), 100);
+  transformation_context.GetFactManager()->ComputeClosureOfFacts(100);
 
   auto replacement_11 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(38, instruction_descriptor_11, 1), 210);
   ASSERT_TRUE(
       replacement_11.IsApplicable(context.get(), transformation_context));
   replacement_11.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %46 with %105[2] in '%62 = OpLogicalAnd %30 %45 %46'
   auto instruction_descriptor_12 =
@@ -1095,7 +1119,8 @@
   ASSERT_TRUE(
       replacement_12.IsApplicable(context.get(), transformation_context));
   replacement_12.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/equivalence_relation_test.cpp b/test/fuzz/equivalence_relation_test.cpp
index 280aa3a..431488e 100644
--- a/test/fuzz/equivalence_relation_test.cpp
+++ b/test/fuzz/equivalence_relation_test.cpp
@@ -14,9 +14,10 @@
 
 #include <set>
 
+#include "source/fuzz/equivalence_relation.h"
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "source/fuzz/equivalence_relation.h"
 
 namespace spvtools {
 namespace fuzz {
diff --git a/test/fuzz/fact_manager/constant_uniform_facts_test.cpp b/test/fuzz/fact_manager/constant_uniform_facts_test.cpp
new file mode 100644
index 0000000..4d80f0a
--- /dev/null
+++ b/test/fuzz/fact_manager/constant_uniform_facts_test.cpp
@@ -0,0 +1,797 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <limits>
+
+#include "source/fuzz/fact_manager/fact_manager.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/uniform_buffer_element_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+using opt::analysis::BoolConstant;
+using opt::analysis::FloatConstant;
+using opt::analysis::IntConstant;
+using opt::analysis::ScalarConstant;
+
+using opt::analysis::Bool;
+using opt::analysis::Float;
+using opt::analysis::Integer;
+using opt::analysis::Type;
+
+bool AddFactHelper(
+    FactManager* fact_manager, const std::vector<uint32_t>& words,
+    const protobufs::UniformBufferElementDescriptor& descriptor) {
+  protobufs::FactConstantUniform constant_uniform_fact;
+  for (auto word : words) {
+    constant_uniform_fact.add_constant_word(word);
+  }
+  *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
+      descriptor;
+  protobufs::Fact fact;
+  *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
+  return fact_manager->MaybeAddFact(fact);
+}
+
+TEST(ConstantUniformFactsTest, ConstantsAvailableViaUniforms) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource GLSL 450
+               OpName %4 "main"
+               OpDecorate %100 DescriptorSet 0
+               OpDecorate %100 Binding 0
+               OpDecorate %200 DescriptorSet 0
+               OpDecorate %200 Binding 1
+               OpDecorate %300 DescriptorSet 0
+               OpDecorate %300 Binding 2
+               OpDecorate %400 DescriptorSet 0
+               OpDecorate %400 Binding 3
+               OpDecorate %500 DescriptorSet 0
+               OpDecorate %500 Binding 4
+               OpDecorate %600 DescriptorSet 0
+               OpDecorate %600 Binding 5
+               OpDecorate %700 DescriptorSet 0
+               OpDecorate %700 Binding 6
+               OpDecorate %800 DescriptorSet 1
+               OpDecorate %800 Binding 0
+               OpDecorate %900 DescriptorSet 1
+               OpDecorate %900 Binding 1
+               OpDecorate %1000 DescriptorSet 1
+               OpDecorate %1000 Binding 2
+               OpDecorate %1100 DescriptorSet 1
+               OpDecorate %1100 Binding 3
+               OpDecorate %1200 DescriptorSet 1
+               OpDecorate %1200 Binding 4
+               OpDecorate %1300 DescriptorSet 1
+               OpDecorate %1300 Binding 5
+               OpDecorate %1400 DescriptorSet 1
+               OpDecorate %1400 Binding 6
+               OpDecorate %1500 DescriptorSet 2
+               OpDecorate %1500 Binding 0
+               OpDecorate %1600 DescriptorSet 2
+               OpDecorate %1600 Binding 1
+               OpDecorate %1700 DescriptorSet 2
+               OpDecorate %1700 Binding 2
+               OpDecorate %1800 DescriptorSet 2
+               OpDecorate %1800 Binding 3
+               OpDecorate %1900 DescriptorSet 2
+               OpDecorate %1900 Binding 4
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpTypeInt 32 1
+         %12 = OpTypeInt 64 0
+         %13 = OpTypeInt 64 1
+         %15 = OpTypeFloat 32
+         %16 = OpTypeFloat 64
+         %17 = OpConstant %11 5
+         %18 = OpConstant %11 20
+         %19 = OpTypeVector %10 4
+         %20 = OpConstant %11 6
+         %21 = OpTypeVector %12 4
+         %22 = OpConstant %11 10
+         %23 = OpTypeVector %11 4
+
+        %102 = OpTypeStruct %10 %10 %23
+        %101 = OpTypePointer Uniform %102
+        %100 = OpVariable %101 Uniform
+
+        %203 = OpTypeArray %23 %17
+        %202 = OpTypeArray %203 %18
+        %201 = OpTypePointer Uniform %202
+        %200 = OpVariable %201 Uniform
+
+        %305 = OpTypeStruct %16 %16 %16 %11 %16
+        %304 = OpTypeStruct %16 %16 %305
+        %303 = OpTypeStruct %304
+        %302 = OpTypeStruct %10 %303
+        %301 = OpTypePointer Uniform %302
+        %300 = OpVariable %301 Uniform
+
+        %400 = OpVariable %101 Uniform
+
+        %500 = OpVariable %201 Uniform
+
+        %604 = OpTypeArray %13 %20
+        %603 = OpTypeArray %604 %20
+        %602 = OpTypeArray %603 %20
+        %601 = OpTypePointer Uniform %602
+        %600 = OpVariable %601 Uniform
+
+        %703 = OpTypeArray %13 %20
+        %702 = OpTypeArray %703 %20
+        %701 = OpTypePointer Uniform %702
+        %700 = OpVariable %701 Uniform
+
+        %802 = OpTypeStruct %702 %602 %19 %202 %302
+        %801 = OpTypePointer Uniform %802
+        %800 = OpVariable %801 Uniform
+
+        %902 = OpTypeStruct %702 %802 %19 %202 %302
+        %901 = OpTypePointer Uniform %902
+        %900 = OpVariable %901 Uniform
+
+       %1003 = OpTypeStruct %802
+       %1002 = OpTypeArray %1003 %20
+       %1001 = OpTypePointer Uniform %1002
+       %1000 = OpVariable %1001 Uniform
+
+       %1101 = OpTypePointer Uniform %21
+       %1100 = OpVariable %1101 Uniform
+
+       %1202 = OpTypeArray %21 %20
+       %1201 = OpTypePointer Uniform %1202
+       %1200 = OpVariable %1201 Uniform
+
+       %1302 = OpTypeArray %21 %20
+       %1301 = OpTypePointer Uniform %1302
+       %1300 = OpVariable %1301 Uniform
+
+       %1402 = OpTypeArray %15 %22
+       %1401 = OpTypePointer Uniform %1402
+       %1400 = OpVariable %1401 Uniform
+
+       %1501 = OpTypePointer Uniform %1402
+       %1500 = OpVariable %1501 Uniform
+
+       %1602 = OpTypeArray %1402 %22
+       %1601 = OpTypePointer Uniform %1602
+       %1600 = OpVariable %1601 Uniform
+
+       %1704 = OpTypeStruct %16 %16 %16
+       %1703 = OpTypeArray %1704 %22
+       %1702 = OpTypeArray %1703 %22
+       %1701 = OpTypePointer Uniform %1702
+       %1700 = OpVariable %1701 Uniform
+
+       %1800 = OpVariable %1701 Uniform
+
+       %1906 = OpTypeStruct %16
+       %1905 = OpTypeStruct %1906
+       %1904 = OpTypeStruct %1905
+       %1903 = OpTypeStruct %1904
+       %1902 = OpTypeStruct %1903
+       %1901 = OpTypePointer Uniform %1902
+       %1900 = OpVariable %1901 Uniform
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  uint32_t buffer_int32_min[1];
+  uint32_t buffer_int64_1[2];
+  uint32_t buffer_int64_max[2];
+  uint32_t buffer_uint64_1[2];
+  uint32_t buffer_uint64_max[2];
+  uint32_t buffer_float_10[1];
+  uint32_t buffer_double_10[2];
+  uint32_t buffer_double_20[2];
+
+  {
+    int32_t temp = std::numeric_limits<int32_t>::min();
+    std::memcpy(&buffer_int32_min, &temp, sizeof(temp));
+  }
+
+  {
+    int64_t temp = 1;
+    std::memcpy(&buffer_int64_1, &temp, sizeof(temp));
+  }
+
+  {
+    int64_t temp = std::numeric_limits<int64_t>::max();
+    std::memcpy(&buffer_int64_max, &temp, sizeof(temp));
+  }
+
+  {
+    uint64_t temp = 1;
+    std::memcpy(&buffer_uint64_1, &temp, sizeof(temp));
+  }
+
+  {
+    uint64_t temp = std::numeric_limits<uint64_t>::max();
+    std::memcpy(&buffer_uint64_max, &temp, sizeof(temp));
+  }
+
+  {
+    float temp = 10.0f;
+    std::memcpy(&buffer_float_10, &temp, sizeof(float));
+  }
+
+  {
+    double temp = 10.0;
+    std::memcpy(&buffer_double_10, &temp, sizeof(temp));
+  }
+
+  {
+    double temp = 20.0;
+    std::memcpy(&buffer_double_20, &temp, sizeof(temp));
+  }
+
+  FactManager fact_manager(context.get());
+
+  uint32_t type_int32_id = 11;
+  uint32_t type_int64_id = 13;
+  uint32_t type_uint32_id = 10;
+  uint32_t type_uint64_id = 12;
+  uint32_t type_float_id = 15;
+  uint32_t type_double_id = 16;
+
+  // Initially there should be no facts about uniforms.
+  ASSERT_TRUE(
+      fact_manager.GetConstantsAvailableFromUniformsForType(type_uint32_id)
+          .empty());
+
+  // In the comments that follow we write v[...][...] to refer to uniform
+  // variable v indexed with some given indices, when in practice v is
+  // identified via a (descriptor set, binding) pair.
+
+  // 100[2][3] == int(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, {1},
+                            MakeUniformBufferElementDescriptor(0, 0, {2, 3})));
+
+  // 200[1][2][3] == int(1)
+  ASSERT_TRUE(AddFactHelper(
+      &fact_manager, {1}, MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3})));
+
+  // 300[1][0][2][3] == int(1)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {1},
+                    MakeUniformBufferElementDescriptor(0, 2, {1, 0, 2, 3})));
+
+  // 400[2][3] = int32_min
+  ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_int32_min[0]},
+                            MakeUniformBufferElementDescriptor(0, 3, {2, 3})));
+
+  // 500[1][2][3] = int32_min
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {buffer_int32_min[0]},
+                    MakeUniformBufferElementDescriptor(0, 4, {1, 2, 3})));
+
+  // 600[1][2][3] = int64_max
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {buffer_int64_max[0], buffer_int64_max[1]},
+                    MakeUniformBufferElementDescriptor(0, 5, {1, 2, 3})));
+
+  // 700[1][1] = int64_max
+  ASSERT_TRUE(AddFactHelper(&fact_manager,
+                            {buffer_int64_max[0], buffer_int64_max[1]},
+                            MakeUniformBufferElementDescriptor(0, 6, {1, 1})));
+
+  // 800[2][3] = uint(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, {1},
+                            MakeUniformBufferElementDescriptor(1, 0, {2, 3})));
+
+  // 900[1][2][3] = uint(1)
+  ASSERT_TRUE(AddFactHelper(
+      &fact_manager, {1}, MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3})));
+
+  // 1000[1][0][2][3] = uint(1)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {1},
+                    MakeUniformBufferElementDescriptor(1, 2, {1, 0, 2, 3})));
+
+  // 1100[0] = uint64(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager,
+                            {buffer_uint64_1[0], buffer_uint64_1[1]},
+                            MakeUniformBufferElementDescriptor(1, 3, {0})));
+
+  // 1200[0][0] = uint64_max
+  ASSERT_TRUE(AddFactHelper(&fact_manager,
+                            {buffer_uint64_max[0], buffer_uint64_max[1]},
+                            MakeUniformBufferElementDescriptor(1, 4, {0, 0})));
+
+  // 1300[1][0] = uint64_max
+  ASSERT_TRUE(AddFactHelper(&fact_manager,
+                            {buffer_uint64_max[0], buffer_uint64_max[1]},
+                            MakeUniformBufferElementDescriptor(1, 5, {1, 0})));
+
+  // 1400[6] = float(10.0)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_float_10[0]},
+                            MakeUniformBufferElementDescriptor(1, 6, {6})));
+
+  // 1500[7] = float(10.0)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_float_10[0]},
+                            MakeUniformBufferElementDescriptor(2, 0, {7})));
+
+  // 1600[9][9] = float(10.0)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, {buffer_float_10[0]},
+                            MakeUniformBufferElementDescriptor(2, 1, {9, 9})));
+
+  // 1700[9][9][1] = double(10.0)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {buffer_double_10[0], buffer_double_10[1]},
+                    MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1})));
+
+  // 1800[9][9][2] = double(10.0)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {buffer_double_10[0], buffer_double_10[1]},
+                    MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2})));
+
+  // 1900[0][0][0][0][0] = double(20.0)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {buffer_double_20[0], buffer_double_20[1]},
+                    MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0})));
+
+  opt::Instruction::OperandList operands = {
+      {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int32_id, 50, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int32_min[0]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int32_id, 51, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int64_id, 52, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_uint32_id, 53, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_uint64_id, 54, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_uint64_id, 55, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_float_10[0]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_float_id, 56, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_double_id, 57, operands));
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[0]}},
+              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[1]}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_double_id, 58, operands));
+
+  // A duplicate of the constant with id 59.
+  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
+  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
+      context.get(), SpvOpConstant, type_int32_id, 59, operands));
+
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+
+  // Constants 1 and int32_min are available.
+  ASSERT_EQ(2,
+            fact_manager.GetConstantsAvailableFromUniformsForType(type_int32_id)
+                .size());
+  // Constant int64_max is available.
+  ASSERT_EQ(1,
+            fact_manager.GetConstantsAvailableFromUniformsForType(type_int64_id)
+                .size());
+  // Constant 1u is available.
+  ASSERT_EQ(
+      1, fact_manager.GetConstantsAvailableFromUniformsForType(type_uint32_id)
+             .size());
+  // Constants 1u and uint64_max are available.
+  ASSERT_EQ(
+      2, fact_manager.GetConstantsAvailableFromUniformsForType(type_uint64_id)
+             .size());
+  // Constant 10.0 is available.
+  ASSERT_EQ(1,
+            fact_manager.GetConstantsAvailableFromUniformsForType(type_float_id)
+                .size());
+  // Constants 10.0 and 20.0 are available.
+  ASSERT_EQ(
+      2, fact_manager.GetConstantsAvailableFromUniformsForType(type_double_id)
+             .size());
+
+  ASSERT_EQ(std::numeric_limits<int64_t>::max(),
+            context->get_constant_mgr()
+                ->FindDeclaredConstant(
+                    fact_manager.GetConstantsAvailableFromUniformsForType(
+                        type_int64_id)[0])
+                ->AsIntConstant()
+                ->GetS64());
+  ASSERT_EQ(1, context->get_constant_mgr()
+                   ->FindDeclaredConstant(
+                       fact_manager.GetConstantsAvailableFromUniformsForType(
+                           type_uint32_id)[0])
+                   ->AsIntConstant()
+                   ->GetU32());
+  ASSERT_EQ(10.0f,
+            context->get_constant_mgr()
+                ->FindDeclaredConstant(
+                    fact_manager.GetConstantsAvailableFromUniformsForType(
+                        type_float_id)[0])
+                ->AsFloatConstant()
+                ->GetFloat());
+  const std::vector<uint32_t>& double_constant_ids =
+      fact_manager.GetConstantsAvailableFromUniformsForType(type_double_id);
+  ASSERT_EQ(10.0, context->get_constant_mgr()
+                      ->FindDeclaredConstant(double_constant_ids[0])
+                      ->AsFloatConstant()
+                      ->GetDouble());
+  ASSERT_EQ(20.0, context->get_constant_mgr()
+                      ->FindDeclaredConstant(double_constant_ids[1])
+                      ->AsFloatConstant()
+                      ->GetDouble());
+
+  const std::vector<protobufs::UniformBufferElementDescriptor>
+      descriptors_for_double_10 =
+          fact_manager.GetUniformDescriptorsForConstant(double_constant_ids[0]);
+  ASSERT_EQ(2, descriptors_for_double_10.size());
+  {
+    auto temp = MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1});
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &temp, &descriptors_for_double_10[0]));
+  }
+  {
+    auto temp = MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2});
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &temp, &descriptors_for_double_10[1]));
+  }
+  const std::vector<protobufs::UniformBufferElementDescriptor>
+      descriptors_for_double_20 =
+          fact_manager.GetUniformDescriptorsForConstant(double_constant_ids[1]);
+  ASSERT_EQ(1, descriptors_for_double_20.size());
+  {
+    auto temp = MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0});
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &temp, &descriptors_for_double_20[0]));
+  }
+
+  auto constant_1_id = fact_manager.GetConstantFromUniformDescriptor(
+      MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2}));
+  ASSERT_TRUE(constant_1_id);
+
+  auto constant_2_id = fact_manager.GetConstantFromUniformDescriptor(
+      MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0}));
+  ASSERT_TRUE(constant_2_id);
+
+  ASSERT_EQ(double_constant_ids[0], constant_1_id);
+
+  ASSERT_EQ(double_constant_ids[1], constant_2_id);
+}
+
+TEST(ConstantUniformFactsTest, TwoConstantsWithSameValue) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "x"
+               OpName %10 "buf"
+               OpMemberName %10 0 "a"
+               OpName %12 ""
+               OpDecorate %8 RelaxedPrecision
+               OpMemberDecorate %10 0 RelaxedPrecision
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %20 = OpConstant %6 1
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  FactManager fact_manager(context.get());
+
+  auto uniform_buffer_element_descriptor =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  // (0, 0, [0]) = int(1)
+  ASSERT_TRUE(
+      AddFactHelper(&fact_manager, {1}, uniform_buffer_element_descriptor));
+  auto constants = fact_manager.GetConstantsAvailableFromUniformsForType(6);
+  ASSERT_EQ(1, constants.size());
+  ASSERT_TRUE(constants[0] == 9 || constants[0] == 20);
+
+  auto constant = fact_manager.GetConstantFromUniformDescriptor(
+      uniform_buffer_element_descriptor);
+  ASSERT_TRUE(constant == 9 || constant == 20);
+
+  // Because the constants with ids 9 and 20 are equal, we should get the same
+  // single uniform buffer element descriptor when we look up the descriptors
+  // for either one of them.
+  for (auto constant_id : {9u, 20u}) {
+    auto descriptors =
+        fact_manager.GetUniformDescriptorsForConstant(constant_id);
+    ASSERT_EQ(1, descriptors.size());
+    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
+        &uniform_buffer_element_descriptor, &descriptors[0]));
+  }
+}
+
+TEST(ConstantUniformFactsTest, NonFiniteFactsAreNotValid) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "f"
+               OpMemberName %7 1 "d"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 8
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %10 = OpTypeFloat 64
+          %7 = OpTypeStruct %6 %10
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  FactManager fact_manager(context.get());
+  auto uniform_buffer_element_descriptor_f =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  auto uniform_buffer_element_descriptor_d =
+      MakeUniformBufferElementDescriptor(0, 0, {1});
+
+  if (std::numeric_limits<float>::has_infinity) {
+    // f == +inf
+    float positive_infinity_float = std::numeric_limits<float>::infinity();
+    uint32_t words[1];
+    memcpy(words, &positive_infinity_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0]},
+                               uniform_buffer_element_descriptor_f));
+    // f == -inf
+    float negative_infinity_float = std::numeric_limits<float>::infinity();
+    memcpy(words, &negative_infinity_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0]},
+                               uniform_buffer_element_descriptor_f));
+  }
+
+  if (std::numeric_limits<float>::has_quiet_NaN) {
+    // f == NaN
+    float quiet_nan_float = std::numeric_limits<float>::quiet_NaN();
+    uint32_t words[1];
+    memcpy(words, &quiet_nan_float, sizeof(float));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0]},
+                               uniform_buffer_element_descriptor_f));
+  }
+
+  if (std::numeric_limits<double>::has_infinity) {
+    // d == +inf
+    double positive_infinity_double = std::numeric_limits<double>::infinity();
+    uint32_t words[2];
+    memcpy(words, &positive_infinity_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+    // d == -inf
+    double negative_infinity_double = -std::numeric_limits<double>::infinity();
+    memcpy(words, &negative_infinity_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+  }
+
+  if (std::numeric_limits<double>::has_quiet_NaN) {
+    // d == NaN
+    double quiet_nan_double = std::numeric_limits<double>::quiet_NaN();
+    uint32_t words[2];
+    memcpy(words, &quiet_nan_double, sizeof(double));
+    ASSERT_FALSE(AddFactHelper(&fact_manager, {words[0], words[1]},
+                               uniform_buffer_element_descriptor_d));
+  }
+}
+
+TEST(ConstantUniformFactsTest, AmbiguousFact) {
+  //  This test came from the following GLSL:
+  //
+  // #version 310 es
+  //
+  // precision highp float;
+  //
+  // layout(set = 0, binding = 0) uniform buf {
+  //   float f;
+  // };
+  //
+  // layout(set = 0, binding = 0) uniform buf2 {
+  //   float g;
+  // };
+  //
+  // void main() {
+  //
+  // }
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "f"
+               OpName %9 ""
+               OpName %10 "buf2"
+               OpMemberName %10 0 "g"
+               OpName %12 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+               OpMemberDecorate %10 0 Offset 0
+               OpDecorate %10 Block
+               OpDecorate %12 DescriptorSet 0
+               OpDecorate %12 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeStruct %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %10 = OpTypeStruct %6
+         %11 = OpTypePointer Uniform %10
+         %12 = OpVariable %11 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  FactManager fact_manager(context.get());
+  auto uniform_buffer_element_descriptor =
+      MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  // The fact cannot be added because it is ambiguous: there are two uniforms
+  // with descriptor set 0 and binding 0.
+  ASSERT_FALSE(
+      AddFactHelper(&fact_manager, {1}, uniform_buffer_element_descriptor));
+}
+
+TEST(ConstantUniformFactsTest, CheckingFactsDoesNotAddConstants) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               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 Uniform %9
+         %11 = OpVariable %10 Uniform
+         %12 = OpConstant %6 0
+         %13 = OpTypePointer Uniform %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %14 = OpAccessChain %13 %11 %12
+         %15 = OpLoad %6 %14
+               OpStore %8 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  FactManager fact_manager(context.get());
+
+  // 8[0] == int(1)
+  ASSERT_TRUE(AddFactHelper(&fact_manager, {1},
+                            MakeUniformBufferElementDescriptor(0, 0, {0})));
+
+  // Although 8[0] has the value 1, we do not have the constant 1 in the module.
+  // We thus should not find any constants available from uniforms for int type.
+  // Furthermore, the act of looking for appropriate constants should not change
+  // which constants are known to the constant manager.
+  auto int_type = context->get_type_mgr()->GetType(6)->AsInteger();
+  opt::analysis::IntConstant constant_one(int_type, {1});
+  ASSERT_FALSE(context->get_constant_mgr()->FindConstant(&constant_one));
+  auto available_constants =
+      fact_manager.GetConstantsAvailableFromUniformsForType(6);
+  ASSERT_EQ(0, available_constants.size());
+  ASSERT_TRUE(IsEqual(env, shader, context.get()));
+  ASSERT_FALSE(context->get_constant_mgr()->FindConstant(&constant_one));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fact_manager/data_synonym_and_id_equation_facts_test.cpp b/test/fuzz/fact_manager/data_synonym_and_id_equation_facts_test.cpp
new file mode 100644
index 0000000..689d2e7
--- /dev/null
+++ b/test/fuzz/fact_manager/data_synonym_and_id_equation_facts_test.cpp
@@ -0,0 +1,691 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/fact_manager.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_merge_blocks.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+void CheckConsistencyOfSynonymFacts(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) {
+  for (uint32_t id : transformation_context.GetFactManager()
+                         ->GetIdsForWhichSynonymsAreKnown()) {
+    // Every id reported by the fact manager should exist in the module.
+    ASSERT_NE(ir_context->get_def_use_mgr()->GetDef(id), nullptr);
+    auto synonyms =
+        transformation_context.GetFactManager()->GetSynonymsForId(id);
+    for (auto& dd : synonyms) {
+      // Every reported synonym should have a base object that exists in the
+      // module.
+      ASSERT_NE(ir_context->get_def_use_mgr()->GetDef(dd->object()), nullptr);
+    }
+  }
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, RecursiveAdditionOfFacts) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 4
+          %8 = OpTypeMatrix %7 4
+          %9 = OpConstant %6 0
+         %10 = OpConstantComposite %7 %9 %9 %9 %9
+         %11 = OpConstantComposite %8 %10 %10 %10 %10
+         %12 = OpFunction %2 None %3
+         %13 = 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));
+
+  FactManager fact_manager(context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
+                                  MakeDataDescriptor(11, {2}));
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {}),
+                                        MakeDataDescriptor(11, {2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {0}),
+                                        MakeDataDescriptor(11, {2, 0})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {1}),
+                                        MakeDataDescriptor(11, {2, 1})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {2}),
+                                        MakeDataDescriptor(11, {2, 2})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {3}),
+                                        MakeDataDescriptor(11, {2, 3})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, CorollaryConversionFacts) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpTypeVector %6 2
+          %9 = OpTypeVector %7 2
+         %10 = OpTypeFloat 32
+         %11 = OpTypeVector %10 2
+         %15 = OpConstant %6 24 ; synonym of %16
+         %16 = OpConstant %6 24
+         %17 = OpConstant %7 24 ; synonym of %18
+         %18 = OpConstant %7 24
+         %19 = OpConstantComposite %8 %15 %15 ; synonym of %20
+         %20 = OpConstantComposite %8 %16 %16
+         %21 = OpConstantComposite %9 %17 %17 ; synonym of %22
+         %22 = OpConstantComposite %9 %18 %18
+         %23 = OpConstantComposite %8 %15 %15 ; not a synonym of %19
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %24 = OpConvertSToF %10 %15 ; synonym of %25
+         %25 = OpConvertSToF %10 %16
+         %26 = OpConvertUToF %10 %17 ; not a synonym of %27 (different opcode)
+         %27 = OpConvertSToF %10 %18
+         %28 = OpConvertUToF %11 %19 ; synonym of %29
+         %29 = OpConvertUToF %11 %20
+         %30 = OpConvertSToF %11 %21 ; not a synonym of %31 (different opcode)
+         %31 = OpConvertUToF %11 %22
+         %32 = OpConvertUToF %11 %23 ; not a synonym of %28 (operand is not synonymous)
+               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));
+
+  FactManager fact_manager(context.get());
+
+  // Add equation facts
+  fact_manager.AddFactIdEquation(24, SpvOpConvertSToF, {15});
+  fact_manager.AddFactIdEquation(25, SpvOpConvertSToF, {16});
+  fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17});
+  fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {18});
+  fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {19});
+  fact_manager.AddFactIdEquation(29, SpvOpConvertUToF, {20});
+  fact_manager.AddFactIdEquation(30, SpvOpConvertSToF, {21});
+  fact_manager.AddFactIdEquation(31, SpvOpConvertUToF, {22});
+  fact_manager.AddFactIdEquation(32, SpvOpConvertUToF, {23});
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}),
+                                  MakeDataDescriptor(16, {}));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
+                                        MakeDataDescriptor(25, {})));
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}),
+                                  MakeDataDescriptor(18, {}));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}),
+                                         MakeDataDescriptor(27, {})));
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(19, {}),
+                                  MakeDataDescriptor(20, {}));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}),
+                                        MakeDataDescriptor(29, {})));
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}),
+                                  MakeDataDescriptor(22, {}));
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}),
+                                         MakeDataDescriptor(31, {})));
+
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
+                                         MakeDataDescriptor(28, {})));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}),
+                                  MakeDataDescriptor(19, {}));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
+                                        MakeDataDescriptor(28, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
+                                        MakeDataDescriptor(29, {})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, HandlesCorollariesWithInvalidIds) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %8 = OpTypeInt 32 1
+          %9 = OpConstant %8 3
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %14 = OpConvertSToF %6 %9
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpPhi %6 %14 %13
+         %15 = OpConvertSToF %6 %9
+         %18 = OpConvertSToF %6 %9
+               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 required facts.
+  transformation_context.GetFactManager()->AddFactIdEquation(
+      14, SpvOpConvertSToF, {9});
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(14, {}), MakeDataDescriptor(17, {}));
+
+  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);
+
+  // Apply TransformationMergeBlocks which will remove %17 from the module.
+  TransformationMergeBlocks transformation(16);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);
+
+  ASSERT_EQ(context->get_def_use_mgr()->GetDef(17), nullptr);
+
+  // Add another equation.
+  transformation_context.GetFactManager()->AddFactIdEquation(
+      15, SpvOpConvertSToF, {9});
+
+  // Check that two ids are synonymous even though one of them doesn't exist in
+  // the module (%17).
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(17, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(14, {})));
+
+  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);
+
+  // Remove some instructions from the module. At this point, the equivalence
+  // class of %14 has no valid members.
+  ASSERT_TRUE(context->KillDef(14));
+  ASSERT_TRUE(context->KillDef(15));
+
+  transformation_context.GetFactManager()->AddFactIdEquation(
+      18, SpvOpConvertSToF, {9});
+
+  CheckConsistencyOfSynonymFacts(context.get(), transformation_context);
+
+  // We don't create synonyms if at least one of the equivalence classes has no
+  // valid members.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(14, {}), MakeDataDescriptor(18, {})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, LogicalNotEquationFacts) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %14 = OpLogicalNot %6 %7
+         %15 = OpCopyObject %6 %7
+         %16 = OpCopyObject %6 %14
+         %17 = OpLogicalNot %6 %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));
+
+  FactManager fact_manager(context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}),
+                                  MakeDataDescriptor(7, {}));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}),
+                                  MakeDataDescriptor(14, {}));
+  fact_manager.AddFactIdEquation(14, SpvOpLogicalNot, {7});
+  fact_manager.AddFactIdEquation(17, SpvOpLogicalNot, {16});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}),
+                                        MakeDataDescriptor(7, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
+                                        MakeDataDescriptor(7, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}),
+                                        MakeDataDescriptor(17, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(16, {}),
+                                        MakeDataDescriptor(14, {})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, SignedNegateEquationFacts) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 24
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %14 = OpSNegate %6 %7
+         %15 = OpSNegate %6 %14
+               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));
+
+  FactManager fact_manager(context.get());
+
+  fact_manager.AddFactIdEquation(14, SpvOpSNegate, {7});
+  fact_manager.AddFactIdEquation(15, SpvOpSNegate, {14});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}),
+                                        MakeDataDescriptor(15, {})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, AddSubNegateFacts1) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %15 = OpConstant %6 24
+         %16 = OpConstant %6 37
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %14 = OpIAdd %6 %15 %16
+         %17 = OpCopyObject %6 %15
+         %18 = OpCopyObject %6 %16
+         %19 = OpISub %6 %14 %18 ; ==> synonymous(%19, %15)
+         %20 = OpISub %6 %14 %17 ; ==> synonymous(%20, %16)
+         %21 = OpCopyObject %6 %14
+         %22 = OpISub %6 %16 %21
+         %23 = OpCopyObject %6 %22
+         %24 = OpSNegate %6 %23 ; ==> synonymous(%24, %15)
+               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));
+
+  FactManager fact_manager(context.get());
+
+  fact_manager.AddFactIdEquation(14, SpvOpIAdd, {15, 16});
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}),
+                                  MakeDataDescriptor(15, {}));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}),
+                                  MakeDataDescriptor(16, {}));
+  fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 18});
+  fact_manager.AddFactIdEquation(20, SpvOpISub, {14, 17});
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}),
+                                  MakeDataDescriptor(14, {}));
+  fact_manager.AddFactIdEquation(22, SpvOpISub, {16, 21});
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}),
+                                  MakeDataDescriptor(22, {}));
+  fact_manager.AddFactIdEquation(24, SpvOpSNegate, {23});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(19, {}),
+                                        MakeDataDescriptor(15, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
+                                        MakeDataDescriptor(16, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
+                                        MakeDataDescriptor(15, {})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, AddSubNegateFacts2) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %15 = OpConstant %6 24
+         %16 = OpConstant %6 37
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %14 = OpISub %6 %15 %16
+         %17 = OpIAdd %6 %14 %16 ; ==> synonymous(%17, %15)
+         %18 = OpIAdd %6 %16 %14 ; ==> synonymous(%17, %18, %15)
+         %19 = OpISub %6 %14 %15
+         %20 = OpSNegate %6 %19 ; ==> synonymous(%20, %16)
+         %21 = OpISub %6 %14 %19 ; ==> synonymous(%21, %15)
+         %22 = OpISub %6 %14 %18
+         %23 = OpSNegate %6 %22 ; ==> synonymous(%23, %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));
+
+  FactManager fact_manager(context.get());
+
+  fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16});
+  fact_manager.AddFactIdEquation(17, SpvOpIAdd, {14, 16});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
+                                        MakeDataDescriptor(15, {})));
+
+  fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 14});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
+                                        MakeDataDescriptor(15, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
+                                        MakeDataDescriptor(18, {})));
+
+  fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15});
+  fact_manager.AddFactIdEquation(20, SpvOpSNegate, {19});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
+                                        MakeDataDescriptor(16, {})));
+
+  fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
+                                        MakeDataDescriptor(15, {})));
+
+  fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18});
+  fact_manager.AddFactIdEquation(23, SpvOpSNegate, {22});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}),
+                                        MakeDataDescriptor(16, {})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, ConversionEquations) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeInt 32 0
+          %6 = OpTypeFloat 32
+         %14 = OpTypeVector %4 2
+         %15 = OpTypeVector %5 2
+         %24 = OpTypeVector %6 2
+         %16 = OpConstant %4 32 ; synonym of %17
+         %17 = OpConstant %4 32
+         %18 = OpConstant %5 32 ; synonym of %19
+         %19 = OpConstant %5 32
+         %20 = OpConstantComposite %14 %16 %16 ; synonym of %21
+         %21 = OpConstantComposite %14 %17 %17
+         %22 = OpConstantComposite %15 %18 %18 ; synonym of %23
+         %23 = OpConstantComposite %15 %19 %19
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %25 = OpConvertUToF %6 %16 ; synonym of %26
+         %26 = OpConvertUToF %6 %17
+         %27 = OpConvertSToF %24 %20 ; not a synonym of %28 (wrong opcode)
+         %28 = OpConvertUToF %24 %21
+         %29 = OpConvertSToF %6 %18 ; not a synonym of %30 (wrong opcode)
+         %30 = OpConvertUToF %6 %19
+         %31 = OpConvertSToF %24 %22 ; synonym of %32
+         %32 = OpConvertSToF %24 %23
+         %33 = OpConvertUToF %6 %17 ; synonym of %26
+         %34 = OpConvertSToF %24 %23 ; synonym of %32
+               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));
+
+  FactManager fact_manager(context.get());
+
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}),
+                                  MakeDataDescriptor(17, {}));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}),
+                                  MakeDataDescriptor(19, {}));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(20, {}),
+                                  MakeDataDescriptor(21, {}));
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}),
+                                  MakeDataDescriptor(23, {}));
+
+  fact_manager.AddFactIdEquation(25, SpvOpConvertUToF, {16});
+  fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}),
+                                        MakeDataDescriptor(26, {})));
+
+  fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {20});
+  fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {21});
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
+                                         MakeDataDescriptor(28, {})));
+
+  fact_manager.AddFactIdEquation(29, SpvOpConvertSToF, {18});
+  fact_manager.AddFactIdEquation(30, SpvOpConvertUToF, {19});
+  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(29, {}),
+                                         MakeDataDescriptor(30, {})));
+
+  fact_manager.AddFactIdEquation(31, SpvOpConvertSToF, {22});
+  fact_manager.AddFactIdEquation(32, SpvOpConvertSToF, {23});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(31, {}),
+                                        MakeDataDescriptor(32, {})));
+
+  fact_manager.AddFactIdEquation(33, SpvOpConvertUToF, {17});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
+                                        MakeDataDescriptor(26, {})));
+
+  fact_manager.AddFactIdEquation(34, SpvOpConvertSToF, {23});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
+                                        MakeDataDescriptor(34, {})));
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, BitcastEquationFacts) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 1
+          %5 = OpTypeInt 32 0
+          %8 = OpTypeFloat 32
+          %9 = OpTypeVector %4 2
+         %10 = OpTypeVector %5 2
+         %11 = OpTypeVector %8 2
+          %6 = OpConstant %4 23
+          %7 = OpConstant %5 23
+         %19 = OpConstant %8 23
+         %20 = OpConstantComposite %9 %6 %6
+         %21 = OpConstantComposite %10 %7 %7
+         %22 = OpConstantComposite %11 %19 %19
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %30 = OpBitcast %8 %6
+         %31 = OpBitcast %5 %6
+         %32 = OpBitcast %8 %7
+         %33 = OpBitcast %4 %7
+         %34 = OpBitcast %4 %19
+         %35 = OpBitcast %5 %19
+         %36 = OpBitcast %10 %20
+         %37 = OpBitcast %11 %20
+         %38 = OpBitcast %9 %21
+         %39 = OpBitcast %11 %21
+         %40 = OpBitcast %9 %22
+         %41 = OpBitcast %10 %22
+               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));
+
+  FactManager fact_manager(context.get());
+
+  uint32_t lhs_id = 30;
+  for (uint32_t rhs_id : {6, 6, 7, 7, 19, 19, 20, 20, 21, 21, 22, 22}) {
+    fact_manager.AddFactIdEquation(lhs_id, SpvOpBitcast, {rhs_id});
+    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(lhs_id, {}),
+                                          MakeDataDescriptor(rhs_id, {})));
+    ++lhs_id;
+  }
+}
+
+TEST(DataSynonymAndIdEquationFactsTest, EquationAndEquivalenceFacts) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %15 = OpConstant %6 24
+         %16 = OpConstant %6 37
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %14 = OpISub %6 %15 %16
+        %114 = OpCopyObject %6 %14
+         %17 = OpIAdd %6 %114 %16 ; ==> synonymous(%17, %15)
+         %18 = OpIAdd %6 %16 %114 ; ==> synonymous(%17, %18, %15)
+         %19 = OpISub %6 %114 %15
+        %119 = OpCopyObject %6 %19
+         %20 = OpSNegate %6 %119 ; ==> synonymous(%20, %16)
+         %21 = OpISub %6 %14 %19 ; ==> synonymous(%21, %15)
+         %22 = OpISub %6 %14 %18
+        %220 = OpCopyObject %6 %22
+         %23 = OpSNegate %6 %220 ; ==> synonymous(%23, %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));
+
+  FactManager fact_manager(context.get());
+
+  fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16});
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(114, {}),
+                                  MakeDataDescriptor(14, {}));
+  fact_manager.AddFactIdEquation(17, SpvOpIAdd, {114, 16});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
+                                        MakeDataDescriptor(15, {})));
+
+  fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 114});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
+                                        MakeDataDescriptor(15, {})));
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
+                                        MakeDataDescriptor(18, {})));
+
+  fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15});
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(119, {}),
+                                  MakeDataDescriptor(19, {}));
+  fact_manager.AddFactIdEquation(20, SpvOpSNegate, {119});
+
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
+                                        MakeDataDescriptor(16, {})));
+
+  fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
+                                        MakeDataDescriptor(15, {})));
+
+  fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18});
+  fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}),
+                                  MakeDataDescriptor(220, {}));
+  fact_manager.AddFactIdEquation(23, SpvOpSNegate, {220});
+  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}),
+                                        MakeDataDescriptor(16, {})));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fact_manager/dead_block_facts_test.cpp b/test/fuzz/fact_manager/dead_block_facts_test.cpp
new file mode 100644
index 0000000..7328fd2
--- /dev/null
+++ b/test/fuzz/fact_manager/dead_block_facts_test.cpp
@@ -0,0 +1,74 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/fact_manager.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(DeadBlockFactsTest, BlockIsDead) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypePointer Function %7
+          %2 = OpFunction %3 None %4
+          %9 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %6 %11 %12
+         %11 = OpLabel
+               OpBranch %10
+         %12 = OpLabel
+               OpBranch %10
+         %10 = 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));
+
+  FactManager fact_manager(context.get());
+
+  ASSERT_FALSE(fact_manager.BlockIsDead(9));
+  ASSERT_FALSE(fact_manager.BlockIsDead(11));
+  ASSERT_FALSE(fact_manager.BlockIsDead(12));
+
+  fact_manager.AddFactBlockIsDead(12);
+
+  ASSERT_FALSE(fact_manager.BlockIsDead(9));
+  ASSERT_FALSE(fact_manager.BlockIsDead(11));
+  ASSERT_TRUE(fact_manager.BlockIsDead(12));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fact_manager/irrelevant_value_facts_test.cpp b/test/fuzz/fact_manager/irrelevant_value_facts_test.cpp
new file mode 100644
index 0000000..9fc0867
--- /dev/null
+++ b/test/fuzz/fact_manager/irrelevant_value_facts_test.cpp
@@ -0,0 +1,179 @@
+// Copyright (c) 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fact_manager/fact_manager.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(IrrelevantValueFactsTest, IdIsIrrelevant) {
+  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
+         %12 = OpConstant %6 0
+         %13 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  FactManager fact_manager(context.get());
+
+  ASSERT_FALSE(fact_manager.IdIsIrrelevant(12));
+  ASSERT_FALSE(fact_manager.IdIsIrrelevant(13));
+
+  fact_manager.AddFactIdIsIrrelevant(12);
+
+  ASSERT_TRUE(fact_manager.IdIsIrrelevant(12));
+  ASSERT_FALSE(fact_manager.IdIsIrrelevant(13));
+}
+
+TEST(IrrelevantValueFactsTest, GetIrrelevantIds) {
+  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
+         %12 = OpConstant %6 0
+         %13 = OpConstant %6 1
+         %14 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  FactManager fact_manager(context.get());
+
+  ASSERT_EQ(fact_manager.GetIrrelevantIds(), std::unordered_set<uint32_t>({}));
+
+  fact_manager.AddFactIdIsIrrelevant(12);
+
+  ASSERT_EQ(fact_manager.GetIrrelevantIds(),
+            std::unordered_set<uint32_t>({12}));
+
+  fact_manager.AddFactIdIsIrrelevant(13);
+
+  ASSERT_EQ(fact_manager.GetIrrelevantIds(),
+            std::unordered_set<uint32_t>({12, 13}));
+}
+
+TEST(IrrelevantValueFactsTest, IdsFromDeadBlocksAreIrrelevant) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypePointer Function %7
+          %9 = OpConstant %7 1
+          %2 = OpFunction %3 None %4
+         %10 = OpLabel
+         %11 = OpVariable %8 Function
+               OpSelectionMerge %12 None
+               OpBranchConditional %6 %13 %14
+         %13 = OpLabel
+               OpBranch %12
+         %14 = OpLabel
+         %15 = OpCopyObject %8 %11
+         %16 = OpCopyObject %7 %9
+         %17 = OpFunctionCall %3 %18
+               OpBranch %12
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %19 = OpLabel
+         %20 = OpVariable %8 Function
+         %21 = OpCopyObject %7 %9
+               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));
+
+  FactManager fact_manager(context.get());
+
+  ASSERT_FALSE(fact_manager.BlockIsDead(14));
+  ASSERT_FALSE(fact_manager.BlockIsDead(19));
+
+  // Initially no id is irrelevant.
+  ASSERT_FALSE(fact_manager.IdIsIrrelevant(16));
+  ASSERT_FALSE(fact_manager.IdIsIrrelevant(17));
+  ASSERT_EQ(fact_manager.GetIrrelevantIds(), std::unordered_set<uint32_t>({}));
+
+  fact_manager.AddFactBlockIsDead(14);
+
+  // %16 and %17 should now be considered irrelevant.
+  ASSERT_TRUE(fact_manager.IdIsIrrelevant(16));
+  ASSERT_TRUE(fact_manager.IdIsIrrelevant(17));
+  ASSERT_EQ(fact_manager.GetIrrelevantIds(),
+            std::unordered_set<uint32_t>({16, 17}));
+
+  // Similarly for %21.
+  ASSERT_FALSE(fact_manager.IdIsIrrelevant(21));
+
+  fact_manager.AddFactBlockIsDead(19);
+
+  ASSERT_TRUE(fact_manager.IdIsIrrelevant(21));
+  ASSERT_EQ(fact_manager.GetIrrelevantIds(),
+            std::unordered_set<uint32_t>({16, 17, 21}));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fact_manager_test.cpp b/test/fuzz/fact_manager_test.cpp
deleted file mode 100644
index bce10b9..0000000
--- a/test/fuzz/fact_manager_test.cpp
+++ /dev/null
@@ -1,1323 +0,0 @@
-// Copyright (c) 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <limits>
-
-#include "source/fuzz/fact_manager.h"
-#include "source/fuzz/uniform_buffer_element_descriptor.h"
-#include "test/fuzz/fuzz_test_util.h"
-
-namespace spvtools {
-namespace fuzz {
-namespace {
-
-using opt::analysis::BoolConstant;
-using opt::analysis::FloatConstant;
-using opt::analysis::IntConstant;
-using opt::analysis::ScalarConstant;
-
-using opt::analysis::Bool;
-using opt::analysis::Float;
-using opt::analysis::Integer;
-using opt::analysis::Type;
-
-bool AddFactHelper(
-    FactManager* fact_manager, opt::IRContext* context,
-    std::vector<uint32_t>&& words,
-    const protobufs::UniformBufferElementDescriptor& descriptor) {
-  protobufs::FactConstantUniform constant_uniform_fact;
-  for (auto word : words) {
-    constant_uniform_fact.add_constant_word(word);
-  }
-  *constant_uniform_fact.mutable_uniform_buffer_element_descriptor() =
-      descriptor;
-  protobufs::Fact fact;
-  *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
-  return fact_manager->AddFact(fact, context);
-}
-
-TEST(FactManagerTest, ConstantsAvailableViaUniforms) {
-  std::string shader = R"(
-               OpCapability Shader
-               OpCapability Int64
-               OpCapability Float64
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource GLSL 450
-               OpName %4 "main"
-               OpDecorate %100 DescriptorSet 0
-               OpDecorate %100 Binding 0
-               OpDecorate %200 DescriptorSet 0
-               OpDecorate %200 Binding 1
-               OpDecorate %300 DescriptorSet 0
-               OpDecorate %300 Binding 2
-               OpDecorate %400 DescriptorSet 0
-               OpDecorate %400 Binding 3
-               OpDecorate %500 DescriptorSet 0
-               OpDecorate %500 Binding 4
-               OpDecorate %600 DescriptorSet 0
-               OpDecorate %600 Binding 5
-               OpDecorate %700 DescriptorSet 0
-               OpDecorate %700 Binding 6
-               OpDecorate %800 DescriptorSet 1
-               OpDecorate %800 Binding 0
-               OpDecorate %900 DescriptorSet 1
-               OpDecorate %900 Binding 1
-               OpDecorate %1000 DescriptorSet 1
-               OpDecorate %1000 Binding 2
-               OpDecorate %1100 DescriptorSet 1
-               OpDecorate %1100 Binding 3
-               OpDecorate %1200 DescriptorSet 1
-               OpDecorate %1200 Binding 4
-               OpDecorate %1300 DescriptorSet 1
-               OpDecorate %1300 Binding 5
-               OpDecorate %1400 DescriptorSet 1
-               OpDecorate %1400 Binding 6
-               OpDecorate %1500 DescriptorSet 2
-               OpDecorate %1500 Binding 0
-               OpDecorate %1600 DescriptorSet 2
-               OpDecorate %1600 Binding 1
-               OpDecorate %1700 DescriptorSet 2
-               OpDecorate %1700 Binding 2
-               OpDecorate %1800 DescriptorSet 2
-               OpDecorate %1800 Binding 3
-               OpDecorate %1900 DescriptorSet 2
-               OpDecorate %1900 Binding 4
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-         %10 = OpTypeInt 32 0
-         %11 = OpTypeInt 32 1
-         %12 = OpTypeInt 64 0
-         %13 = OpTypeInt 64 1
-         %15 = OpTypeFloat 32
-         %16 = OpTypeFloat 64
-         %17 = OpConstant %11 5
-         %18 = OpConstant %11 20
-         %19 = OpTypeVector %10 4
-         %20 = OpConstant %11 6
-         %21 = OpTypeVector %12 4
-         %22 = OpConstant %11 10
-         %23 = OpTypeVector %11 4
-
-        %102 = OpTypeStruct %10 %10 %23
-        %101 = OpTypePointer Uniform %102
-        %100 = OpVariable %101 Uniform
-
-        %203 = OpTypeArray %23 %17
-        %202 = OpTypeArray %203 %18
-        %201 = OpTypePointer Uniform %202
-        %200 = OpVariable %201 Uniform
-
-        %305 = OpTypeStruct %16 %16 %16 %11 %16
-        %304 = OpTypeStruct %16 %16 %305
-        %303 = OpTypeStruct %304
-        %302 = OpTypeStruct %10 %303
-        %301 = OpTypePointer Uniform %302
-        %300 = OpVariable %301 Uniform
-
-        %400 = OpVariable %101 Uniform
-
-        %500 = OpVariable %201 Uniform
-
-        %604 = OpTypeArray %13 %20
-        %603 = OpTypeArray %604 %20
-        %602 = OpTypeArray %603 %20
-        %601 = OpTypePointer Uniform %602
-        %600 = OpVariable %601 Uniform
-
-        %703 = OpTypeArray %13 %20
-        %702 = OpTypeArray %703 %20
-        %701 = OpTypePointer Uniform %702
-        %700 = OpVariable %701 Uniform
-
-        %802 = OpTypeStruct %702 %602 %19 %202 %302
-        %801 = OpTypePointer Uniform %802
-        %800 = OpVariable %801 Uniform
-
-        %902 = OpTypeStruct %702 %802 %19 %202 %302
-        %901 = OpTypePointer Uniform %902
-        %900 = OpVariable %901 Uniform
-
-       %1003 = OpTypeStruct %802
-       %1002 = OpTypeArray %1003 %20
-       %1001 = OpTypePointer Uniform %1002
-       %1000 = OpVariable %1001 Uniform
-
-       %1101 = OpTypePointer Uniform %21
-       %1100 = OpVariable %1101 Uniform
-
-       %1202 = OpTypeArray %21 %20
-       %1201 = OpTypePointer Uniform %1202
-       %1200 = OpVariable %1201 Uniform
-
-       %1302 = OpTypeArray %21 %20
-       %1301 = OpTypePointer Uniform %1302
-       %1300 = OpVariable %1301 Uniform
-
-       %1402 = OpTypeArray %15 %22
-       %1401 = OpTypePointer Uniform %1402
-       %1400 = OpVariable %1401 Uniform
-
-       %1501 = OpTypePointer Uniform %1402
-       %1500 = OpVariable %1501 Uniform
-
-       %1602 = OpTypeArray %1402 %22
-       %1601 = OpTypePointer Uniform %1602
-       %1600 = OpVariable %1601 Uniform
-
-       %1704 = OpTypeStruct %16 %16 %16
-       %1703 = OpTypeArray %1704 %22
-       %1702 = OpTypeArray %1703 %22
-       %1701 = OpTypePointer Uniform %1702
-       %1700 = OpVariable %1701 Uniform
-
-       %1800 = OpVariable %1701 Uniform
-
-       %1906 = OpTypeStruct %16
-       %1905 = OpTypeStruct %1906
-       %1904 = OpTypeStruct %1905
-       %1903 = OpTypeStruct %1904
-       %1902 = OpTypeStruct %1903
-       %1901 = OpTypePointer Uniform %1902
-       %1900 = OpVariable %1901 Uniform
-
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  uint32_t buffer_int32_min[1];
-  uint32_t buffer_int64_1[2];
-  uint32_t buffer_int64_max[2];
-  uint32_t buffer_uint64_1[2];
-  uint32_t buffer_uint64_max[2];
-  uint32_t buffer_float_10[1];
-  uint32_t buffer_double_10[2];
-  uint32_t buffer_double_20[2];
-
-  {
-    int32_t temp = std::numeric_limits<int32_t>::min();
-    std::memcpy(&buffer_int32_min, &temp, sizeof(temp));
-  }
-
-  {
-    int64_t temp = 1;
-    std::memcpy(&buffer_int64_1, &temp, sizeof(temp));
-  }
-
-  {
-    int64_t temp = std::numeric_limits<int64_t>::max();
-    std::memcpy(&buffer_int64_max, &temp, sizeof(temp));
-  }
-
-  {
-    uint64_t temp = 1;
-    std::memcpy(&buffer_uint64_1, &temp, sizeof(temp));
-  }
-
-  {
-    uint64_t temp = std::numeric_limits<uint64_t>::max();
-    std::memcpy(&buffer_uint64_max, &temp, sizeof(temp));
-  }
-
-  {
-    float temp = 10.0f;
-    std::memcpy(&buffer_float_10, &temp, sizeof(float));
-  }
-
-  {
-    double temp = 10.0;
-    std::memcpy(&buffer_double_10, &temp, sizeof(temp));
-  }
-
-  {
-    double temp = 20.0;
-    std::memcpy(&buffer_double_20, &temp, sizeof(temp));
-  }
-
-  FactManager fact_manager;
-
-  uint32_t type_int32_id = 11;
-  uint32_t type_int64_id = 13;
-  uint32_t type_uint32_id = 10;
-  uint32_t type_uint64_id = 12;
-  uint32_t type_float_id = 15;
-  uint32_t type_double_id = 16;
-
-  // Initially there should be no facts about uniforms.
-  ASSERT_TRUE(fact_manager
-                  .GetConstantsAvailableFromUniformsForType(context.get(),
-                                                            type_uint32_id)
-                  .empty());
-
-  // In the comments that follow we write v[...][...] to refer to uniform
-  // variable v indexed with some given indices, when in practice v is
-  // identified via a (descriptor set, binding) pair.
-
-  // 100[2][3] == int(1)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
-                            MakeUniformBufferElementDescriptor(0, 0, {2, 3})));
-
-  // 200[1][2][3] == int(1)
-  ASSERT_TRUE(
-      AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(0, 1, {1, 2, 3})));
-
-  // 300[1][0][2][3] == int(1)
-  ASSERT_TRUE(
-      AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(0, 2, {1, 0, 2, 3})));
-
-  // 400[2][3] = int32_min
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
-                            MakeUniformBufferElementDescriptor(0, 3, {2, 3})));
-
-  // 500[1][2][3] = int32_min
-  ASSERT_TRUE(
-      AddFactHelper(&fact_manager, context.get(), {buffer_int32_min[0]},
-                    MakeUniformBufferElementDescriptor(0, 4, {1, 2, 3})));
-
-  // 600[1][2][3] = int64_max
-  ASSERT_TRUE(AddFactHelper(
-      &fact_manager, context.get(), {buffer_int64_max[0], buffer_int64_max[1]},
-      MakeUniformBufferElementDescriptor(0, 5, {1, 2, 3})));
-
-  // 700[1][1] = int64_max
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
-                            {buffer_int64_max[0], buffer_int64_max[1]},
-                            MakeUniformBufferElementDescriptor(0, 6, {1, 1})));
-
-  // 800[2][3] = uint(1)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
-                            MakeUniformBufferElementDescriptor(1, 0, {2, 3})));
-
-  // 900[1][2][3] = uint(1)
-  ASSERT_TRUE(
-      AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(1, 1, {1, 2, 3})));
-
-  // 1000[1][0][2][3] = uint(1)
-  ASSERT_TRUE(
-      AddFactHelper(&fact_manager, context.get(), {1},
-                    MakeUniformBufferElementDescriptor(1, 2, {1, 0, 2, 3})));
-
-  // 1100[0] = uint64(1)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
-                            {buffer_uint64_1[0], buffer_uint64_1[1]},
-                            MakeUniformBufferElementDescriptor(1, 3, {0})));
-
-  // 1200[0][0] = uint64_max
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
-                            {buffer_uint64_max[0], buffer_uint64_max[1]},
-                            MakeUniformBufferElementDescriptor(1, 4, {0, 0})));
-
-  // 1300[1][0] = uint64_max
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(),
-                            {buffer_uint64_max[0], buffer_uint64_max[1]},
-                            MakeUniformBufferElementDescriptor(1, 5, {1, 0})));
-
-  // 1400[6] = float(10.0)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
-                            MakeUniformBufferElementDescriptor(1, 6, {6})));
-
-  // 1500[7] = float(10.0)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
-                            MakeUniformBufferElementDescriptor(2, 0, {7})));
-
-  // 1600[9][9] = float(10.0)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {buffer_float_10[0]},
-                            MakeUniformBufferElementDescriptor(2, 1, {9, 9})));
-
-  // 1700[9][9][1] = double(10.0)
-  ASSERT_TRUE(AddFactHelper(
-      &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
-      MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1})));
-
-  // 1800[9][9][2] = double(10.0)
-  ASSERT_TRUE(AddFactHelper(
-      &fact_manager, context.get(), {buffer_double_10[0], buffer_double_10[1]},
-      MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2})));
-
-  // 1900[0][0][0][0][0] = double(20.0)
-  ASSERT_TRUE(AddFactHelper(
-      &fact_manager, context.get(), {buffer_double_20[0], buffer_double_20[1]},
-      MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0})));
-
-  opt::Instruction::OperandList operands = {
-      {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_int32_id, 50, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int32_min[0]}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_int32_id, 51, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[0]}},
-              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_int64_max[1]}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_int64_id, 52, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_uint32_id, 53, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[0]}},
-              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_1[1]}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_uint64_id, 54, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[0]}},
-              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_uint64_max[1]}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_uint64_id, 55, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_float_10[0]}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_float_id, 56, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[0]}},
-              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_10[1]}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_double_id, 57, operands));
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[0]}},
-              {SPV_OPERAND_TYPE_LITERAL_INTEGER, {buffer_double_20[1]}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_double_id, 58, operands));
-
-  // A duplicate of the constant with id 59.
-  operands = {{SPV_OPERAND_TYPE_LITERAL_INTEGER, {1}}};
-  context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
-      context.get(), SpvOpConstant, type_int32_id, 59, operands));
-
-  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
-
-  // Constants 1 and int32_min are available.
-  ASSERT_EQ(2, fact_manager
-                   .GetConstantsAvailableFromUniformsForType(context.get(),
-                                                             type_int32_id)
-                   .size());
-  // Constant int64_max is available.
-  ASSERT_EQ(1, fact_manager
-                   .GetConstantsAvailableFromUniformsForType(context.get(),
-                                                             type_int64_id)
-                   .size());
-  // Constant 1u is available.
-  ASSERT_EQ(1, fact_manager
-                   .GetConstantsAvailableFromUniformsForType(context.get(),
-                                                             type_uint32_id)
-                   .size());
-  // Constants 1u and uint64_max are available.
-  ASSERT_EQ(2, fact_manager
-                   .GetConstantsAvailableFromUniformsForType(context.get(),
-                                                             type_uint64_id)
-                   .size());
-  // Constant 10.0 is available.
-  ASSERT_EQ(1, fact_manager
-                   .GetConstantsAvailableFromUniformsForType(context.get(),
-                                                             type_float_id)
-                   .size());
-  // Constants 10.0 and 20.0 are available.
-  ASSERT_EQ(2, fact_manager
-                   .GetConstantsAvailableFromUniformsForType(context.get(),
-                                                             type_double_id)
-                   .size());
-
-  ASSERT_EQ(std::numeric_limits<int64_t>::max(),
-            context->get_constant_mgr()
-                ->FindDeclaredConstant(
-                    fact_manager.GetConstantsAvailableFromUniformsForType(
-                        context.get(), type_int64_id)[0])
-                ->AsIntConstant()
-                ->GetS64());
-  ASSERT_EQ(1, context->get_constant_mgr()
-                   ->FindDeclaredConstant(
-                       fact_manager.GetConstantsAvailableFromUniformsForType(
-                           context.get(), type_uint32_id)[0])
-                   ->AsIntConstant()
-                   ->GetU32());
-  ASSERT_EQ(10.0f,
-            context->get_constant_mgr()
-                ->FindDeclaredConstant(
-                    fact_manager.GetConstantsAvailableFromUniformsForType(
-                        context.get(), type_float_id)[0])
-                ->AsFloatConstant()
-                ->GetFloat());
-  const std::vector<uint32_t>& double_constant_ids =
-      fact_manager.GetConstantsAvailableFromUniformsForType(context.get(),
-                                                            type_double_id);
-  ASSERT_EQ(10.0, context->get_constant_mgr()
-                      ->FindDeclaredConstant(double_constant_ids[0])
-                      ->AsFloatConstant()
-                      ->GetDouble());
-  ASSERT_EQ(20.0, context->get_constant_mgr()
-                      ->FindDeclaredConstant(double_constant_ids[1])
-                      ->AsFloatConstant()
-                      ->GetDouble());
-
-  const std::vector<protobufs::UniformBufferElementDescriptor>
-      descriptors_for_double_10 = fact_manager.GetUniformDescriptorsForConstant(
-          context.get(), double_constant_ids[0]);
-  ASSERT_EQ(2, descriptors_for_double_10.size());
-  {
-    auto temp = MakeUniformBufferElementDescriptor(2, 2, {9, 9, 1});
-    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
-        &temp, &descriptors_for_double_10[0]));
-  }
-  {
-    auto temp = MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2});
-    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
-        &temp, &descriptors_for_double_10[1]));
-  }
-  const std::vector<protobufs::UniformBufferElementDescriptor>
-      descriptors_for_double_20 = fact_manager.GetUniformDescriptorsForConstant(
-          context.get(), double_constant_ids[1]);
-  ASSERT_EQ(1, descriptors_for_double_20.size());
-  {
-    auto temp = MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0});
-    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
-        &temp, &descriptors_for_double_20[0]));
-  }
-
-  auto constant_1_id = fact_manager.GetConstantFromUniformDescriptor(
-      context.get(), MakeUniformBufferElementDescriptor(2, 3, {9, 9, 2}));
-  ASSERT_TRUE(constant_1_id);
-
-  auto constant_2_id = fact_manager.GetConstantFromUniformDescriptor(
-      context.get(), MakeUniformBufferElementDescriptor(2, 4, {0, 0, 0, 0, 0}));
-  ASSERT_TRUE(constant_2_id);
-
-  ASSERT_EQ(double_constant_ids[0], constant_1_id);
-
-  ASSERT_EQ(double_constant_ids[1], constant_2_id);
-}
-
-TEST(FactManagerTest, TwoConstantsWithSameValue) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "x"
-               OpName %10 "buf"
-               OpMemberName %10 0 "a"
-               OpName %12 ""
-               OpDecorate %8 RelaxedPrecision
-               OpMemberDecorate %10 0 RelaxedPrecision
-               OpMemberDecorate %10 0 Offset 0
-               OpDecorate %10 Block
-               OpDecorate %12 DescriptorSet 0
-               OpDecorate %12 Binding 0
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 1
-         %20 = OpConstant %6 1
-         %10 = OpTypeStruct %6
-         %11 = OpTypePointer Uniform %10
-         %12 = OpVariable %11 Uniform
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-               OpStore %8 %9
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  auto uniform_buffer_element_descriptor =
-      MakeUniformBufferElementDescriptor(0, 0, {0});
-
-  // (0, 0, [0]) = int(1)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
-                            uniform_buffer_element_descriptor));
-  auto constants =
-      fact_manager.GetConstantsAvailableFromUniformsForType(context.get(), 6);
-  ASSERT_EQ(1, constants.size());
-  ASSERT_TRUE(constants[0] == 9 || constants[0] == 20);
-
-  auto constant = fact_manager.GetConstantFromUniformDescriptor(
-      context.get(), uniform_buffer_element_descriptor);
-  ASSERT_TRUE(constant == 9 || constant == 20);
-
-  // Because the constants with ids 9 and 20 are equal, we should get the same
-  // single uniform buffer element descriptor when we look up the descriptors
-  // for either one of them.
-  for (auto constant_id : {9u, 20u}) {
-    auto descriptors = fact_manager.GetUniformDescriptorsForConstant(
-        context.get(), constant_id);
-    ASSERT_EQ(1, descriptors.size());
-    ASSERT_TRUE(UniformBufferElementDescriptorEquals()(
-        &uniform_buffer_element_descriptor, &descriptors[0]));
-  }
-}
-
-TEST(FactManagerTest, NonFiniteFactsAreNotValid) {
-  std::string shader = R"(
-               OpCapability Shader
-               OpCapability Float64
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %7 "buf"
-               OpMemberName %7 0 "f"
-               OpMemberName %7 1 "d"
-               OpName %9 ""
-               OpMemberDecorate %7 0 Offset 0
-               OpMemberDecorate %7 1 Offset 8
-               OpDecorate %7 Block
-               OpDecorate %9 DescriptorSet 0
-               OpDecorate %9 Binding 0
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-         %10 = OpTypeFloat 64
-          %7 = OpTypeStruct %6 %10
-          %8 = OpTypePointer Uniform %7
-          %9 = OpVariable %8 Uniform
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-  auto uniform_buffer_element_descriptor_f =
-      MakeUniformBufferElementDescriptor(0, 0, {0});
-
-  auto uniform_buffer_element_descriptor_d =
-      MakeUniformBufferElementDescriptor(0, 0, {1});
-
-  if (std::numeric_limits<float>::has_infinity) {
-    // f == +inf
-    float positive_infinity_float = std::numeric_limits<float>::infinity();
-    uint32_t words[1];
-    memcpy(words, &positive_infinity_float, sizeof(float));
-    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
-                               uniform_buffer_element_descriptor_f));
-    // f == -inf
-    float negative_infinity_float = std::numeric_limits<float>::infinity();
-    memcpy(words, &negative_infinity_float, sizeof(float));
-    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
-                               uniform_buffer_element_descriptor_f));
-  }
-
-  if (std::numeric_limits<float>::has_quiet_NaN) {
-    // f == NaN
-    float quiet_nan_float = std::numeric_limits<float>::quiet_NaN();
-    uint32_t words[1];
-    memcpy(words, &quiet_nan_float, sizeof(float));
-    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {words[0]},
-                               uniform_buffer_element_descriptor_f));
-  }
-
-  if (std::numeric_limits<double>::has_infinity) {
-    // d == +inf
-    double positive_infinity_double = std::numeric_limits<double>::infinity();
-    uint32_t words[2];
-    memcpy(words, &positive_infinity_double, sizeof(double));
-    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
-                               {words[0], words[1]},
-                               uniform_buffer_element_descriptor_d));
-    // d == -inf
-    double negative_infinity_double = -std::numeric_limits<double>::infinity();
-    memcpy(words, &negative_infinity_double, sizeof(double));
-    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
-                               {words[0], words[1]},
-                               uniform_buffer_element_descriptor_d));
-  }
-
-  if (std::numeric_limits<double>::has_quiet_NaN) {
-    // d == NaN
-    double quiet_nan_double = std::numeric_limits<double>::quiet_NaN();
-    uint32_t words[2];
-    memcpy(words, &quiet_nan_double, sizeof(double));
-    ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(),
-                               {words[0], words[1]},
-                               uniform_buffer_element_descriptor_d));
-  }
-}
-
-TEST(FactManagerTest, AmbiguousFact) {
-  //  This test came from the following GLSL:
-  //
-  // #version 310 es
-  //
-  // precision highp float;
-  //
-  // layout(set = 0, binding = 0) uniform buf {
-  //   float f;
-  // };
-  //
-  // layout(set = 0, binding = 0) uniform buf2 {
-  //   float g;
-  // };
-  //
-  // void main() {
-  //
-  // }
-
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %7 "buf"
-               OpMemberName %7 0 "f"
-               OpName %9 ""
-               OpName %10 "buf2"
-               OpMemberName %10 0 "g"
-               OpName %12 ""
-               OpMemberDecorate %7 0 Offset 0
-               OpDecorate %7 Block
-               OpDecorate %9 DescriptorSet 0
-               OpDecorate %9 Binding 0
-               OpMemberDecorate %10 0 Offset 0
-               OpDecorate %10 Block
-               OpDecorate %12 DescriptorSet 0
-               OpDecorate %12 Binding 0
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypeStruct %6
-          %8 = OpTypePointer Uniform %7
-          %9 = OpVariable %8 Uniform
-         %10 = OpTypeStruct %6
-         %11 = OpTypePointer Uniform %10
-         %12 = OpVariable %11 Uniform
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-  auto uniform_buffer_element_descriptor =
-      MakeUniformBufferElementDescriptor(0, 0, {0});
-
-  // The fact cannot be added because it is ambiguous: there are two uniforms
-  // with descriptor set 0 and binding 0.
-  ASSERT_FALSE(AddFactHelper(&fact_manager, context.get(), {1},
-                             uniform_buffer_element_descriptor));
-}
-
-TEST(FactManagerTest, RecursiveAdditionOfFacts) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypeVector %6 4
-          %8 = OpTypeMatrix %7 4
-          %9 = OpConstant %6 0
-         %10 = OpConstantComposite %7 %9 %9 %9 %9
-         %11 = OpConstantComposite %8 %10 %10 %10 %10
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(10, {}),
-                                  MakeDataDescriptor(11, {2}), context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {}),
-                                        MakeDataDescriptor(11, {2})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {0}),
-                                        MakeDataDescriptor(11, {2, 0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {1}),
-                                        MakeDataDescriptor(11, {2, 1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {2}),
-                                        MakeDataDescriptor(11, {2, 2})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(10, {3}),
-                                        MakeDataDescriptor(11, {2, 3})));
-}
-
-TEST(FactManagerTest, CorollaryConversionFacts) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypeInt 32 0
-          %8 = OpTypeVector %6 2
-          %9 = OpTypeVector %7 2
-         %10 = OpTypeFloat 32
-         %11 = OpTypeVector %10 2
-         %15 = OpConstant %6 24 ; synonym of %16
-         %16 = OpConstant %6 24
-         %17 = OpConstant %7 24 ; synonym of %18
-         %18 = OpConstant %7 24
-         %19 = OpConstantComposite %8 %15 %15 ; synonym of %20
-         %20 = OpConstantComposite %8 %16 %16
-         %21 = OpConstantComposite %9 %17 %17 ; synonym of %22
-         %22 = OpConstantComposite %9 %18 %18
-         %23 = OpConstantComposite %8 %15 %15 ; not a synonym of %19
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-         %24 = OpConvertSToF %10 %15 ; synonym of %25
-         %25 = OpConvertSToF %10 %16
-         %26 = OpConvertUToF %10 %17 ; not a synonym of %27 (different opcode)
-         %27 = OpConvertSToF %10 %18
-         %28 = OpConvertUToF %11 %19 ; synonym of %29
-         %29 = OpConvertUToF %11 %20
-         %30 = OpConvertSToF %11 %21 ; not a synonym of %31 (different opcode)
-         %31 = OpConvertUToF %11 %22
-         %32 = OpConvertUToF %11 %23 ; not a synonym of %28 (operand is not synonymous)
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  // Add equation facts
-  fact_manager.AddFactIdEquation(24, SpvOpConvertSToF, {15}, context.get());
-  fact_manager.AddFactIdEquation(25, SpvOpConvertSToF, {16}, context.get());
-  fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17}, context.get());
-  fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {18}, context.get());
-  fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {19}, context.get());
-  fact_manager.AddFactIdEquation(29, SpvOpConvertUToF, {20}, context.get());
-  fact_manager.AddFactIdEquation(30, SpvOpConvertSToF, {21}, context.get());
-  fact_manager.AddFactIdEquation(31, SpvOpConvertUToF, {22}, context.get());
-  fact_manager.AddFactIdEquation(32, SpvOpConvertUToF, {23}, context.get());
-
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}),
-                                  MakeDataDescriptor(16, {}), context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
-                                        MakeDataDescriptor(25, {})));
-
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}),
-                                  MakeDataDescriptor(18, {}), context.get());
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(26, {}),
-                                         MakeDataDescriptor(27, {})));
-
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(19, {}),
-                                  MakeDataDescriptor(20, {}), context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}),
-                                        MakeDataDescriptor(29, {})));
-
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}),
-                                  MakeDataDescriptor(22, {}), context.get());
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}),
-                                         MakeDataDescriptor(31, {})));
-
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
-                                         MakeDataDescriptor(28, {})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}),
-                                  MakeDataDescriptor(19, {}), context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
-                                        MakeDataDescriptor(28, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
-                                        MakeDataDescriptor(29, {})));
-}
-
-TEST(FactManagerTest, LogicalNotEquationFacts) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeBool
-          %7 = OpConstantTrue %6
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-         %14 = OpLogicalNot %6 %7
-         %15 = OpCopyObject %6 %7
-         %16 = OpCopyObject %6 %14
-         %17 = OpLogicalNot %6 %16
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(15, {}),
-                                  MakeDataDescriptor(7, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}),
-                                  MakeDataDescriptor(14, {}), context.get());
-  fact_manager.AddFactIdEquation(14, SpvOpLogicalNot, {7}, context.get());
-  fact_manager.AddFactIdEquation(17, SpvOpLogicalNot, {16}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}),
-                                        MakeDataDescriptor(7, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
-                                        MakeDataDescriptor(7, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(15, {}),
-                                        MakeDataDescriptor(17, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(16, {}),
-                                        MakeDataDescriptor(14, {})));
-}
-
-TEST(FactManagerTest, SignedNegateEquationFacts) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpConstant %6 24
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-         %14 = OpSNegate %6 %7
-         %15 = OpSNegate %6 %14
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  fact_manager.AddFactIdEquation(14, SpvOpSNegate, {7}, context.get());
-  fact_manager.AddFactIdEquation(15, SpvOpSNegate, {14}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(7, {}),
-                                        MakeDataDescriptor(15, {})));
-}
-
-TEST(FactManagerTest, AddSubNegateFacts1) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-         %15 = OpConstant %6 24
-         %16 = OpConstant %6 37
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-         %14 = OpIAdd %6 %15 %16
-         %17 = OpCopyObject %6 %15
-         %18 = OpCopyObject %6 %16
-         %19 = OpISub %6 %14 %18 ; ==> synonymous(%19, %15)
-         %20 = OpISub %6 %14 %17 ; ==> synonymous(%20, %16)
-         %21 = OpCopyObject %6 %14
-         %22 = OpISub %6 %16 %21
-         %23 = OpCopyObject %6 %22
-         %24 = OpSNegate %6 %23 ; ==> synonymous(%24, %15)
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  fact_manager.AddFactIdEquation(14, SpvOpIAdd, {15, 16}, context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(17, {}),
-                                  MakeDataDescriptor(15, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}),
-                                  MakeDataDescriptor(16, {}), context.get());
-  fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 18}, context.get());
-  fact_manager.AddFactIdEquation(20, SpvOpISub, {14, 17}, context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {}),
-                                  MakeDataDescriptor(14, {}), context.get());
-  fact_manager.AddFactIdEquation(22, SpvOpISub, {16, 21}, context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(23, {}),
-                                  MakeDataDescriptor(22, {}), context.get());
-  fact_manager.AddFactIdEquation(24, SpvOpSNegate, {23}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(19, {}),
-                                        MakeDataDescriptor(15, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
-                                        MakeDataDescriptor(16, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
-                                        MakeDataDescriptor(15, {})));
-}
-
-TEST(FactManagerTest, AddSubNegateFacts2) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-         %15 = OpConstant %6 24
-         %16 = OpConstant %6 37
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-         %14 = OpISub %6 %15 %16
-         %17 = OpIAdd %6 %14 %16 ; ==> synonymous(%17, %15)
-         %18 = OpIAdd %6 %16 %14 ; ==> synonymous(%17, %18, %15)
-         %19 = OpISub %6 %14 %15
-         %20 = OpSNegate %6 %19 ; ==> synonymous(%20, %16)
-         %21 = OpISub %6 %14 %19 ; ==> synonymous(%21, %15)
-         %22 = OpISub %6 %14 %18
-         %23 = OpSNegate %6 %22 ; ==> synonymous(%23, %16)
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16}, context.get());
-  fact_manager.AddFactIdEquation(17, SpvOpIAdd, {14, 16}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
-                                        MakeDataDescriptor(15, {})));
-
-  fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 14}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
-                                        MakeDataDescriptor(15, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
-                                        MakeDataDescriptor(18, {})));
-
-  fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15}, context.get());
-  fact_manager.AddFactIdEquation(20, SpvOpSNegate, {19}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
-                                        MakeDataDescriptor(16, {})));
-
-  fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                        MakeDataDescriptor(15, {})));
-
-  fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18}, context.get());
-  fact_manager.AddFactIdEquation(23, SpvOpSNegate, {22}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}),
-                                        MakeDataDescriptor(16, {})));
-}
-
-TEST(FactManagerTest, ConversionEquations) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %4 = OpTypeInt 32 1
-          %5 = OpTypeInt 32 0
-          %6 = OpTypeFloat 32
-         %14 = OpTypeVector %4 2
-         %15 = OpTypeVector %5 2
-         %24 = OpTypeVector %6 2
-         %16 = OpConstant %4 32 ; synonym of %17
-         %17 = OpConstant %4 32
-         %18 = OpConstant %5 32 ; synonym of %19
-         %19 = OpConstant %5 32
-         %20 = OpConstantComposite %14 %16 %16 ; synonym of %21
-         %21 = OpConstantComposite %14 %17 %17
-         %22 = OpConstantComposite %15 %18 %18 ; synonym of %23
-         %23 = OpConstantComposite %15 %19 %19
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-         %25 = OpConvertUToF %6 %16 ; synonym of %26
-         %26 = OpConvertUToF %6 %17
-         %27 = OpConvertSToF %24 %20 ; not a synonym of %28 (wrong opcode)
-         %28 = OpConvertUToF %24 %21
-         %29 = OpConvertSToF %6 %18 ; not a synonym of %30 (wrong opcode)
-         %30 = OpConvertUToF %6 %19
-         %31 = OpConvertSToF %24 %22 ; synonym of %32
-         %32 = OpConvertSToF %24 %23
-         %33 = OpConvertUToF %6 %17 ; synonym of %26
-         %34 = OpConvertSToF %24 %23 ; synonym of %32
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(16, {}),
-                                  MakeDataDescriptor(17, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(18, {}),
-                                  MakeDataDescriptor(19, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(20, {}),
-                                  MakeDataDescriptor(21, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}),
-                                  MakeDataDescriptor(23, {}), context.get());
-
-  fact_manager.AddFactIdEquation(25, SpvOpConvertUToF, {16}, context.get());
-  fact_manager.AddFactIdEquation(26, SpvOpConvertUToF, {17}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}),
-                                        MakeDataDescriptor(26, {})));
-
-  fact_manager.AddFactIdEquation(27, SpvOpConvertSToF, {20}, context.get());
-  fact_manager.AddFactIdEquation(28, SpvOpConvertUToF, {21}, context.get());
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
-                                         MakeDataDescriptor(28, {})));
-
-  fact_manager.AddFactIdEquation(29, SpvOpConvertSToF, {18}, context.get());
-  fact_manager.AddFactIdEquation(30, SpvOpConvertUToF, {19}, context.get());
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(29, {}),
-                                         MakeDataDescriptor(30, {})));
-
-  fact_manager.AddFactIdEquation(31, SpvOpConvertSToF, {22}, context.get());
-  fact_manager.AddFactIdEquation(32, SpvOpConvertSToF, {23}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(31, {}),
-                                        MakeDataDescriptor(32, {})));
-
-  fact_manager.AddFactIdEquation(33, SpvOpConvertUToF, {17}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
-                                        MakeDataDescriptor(26, {})));
-
-  fact_manager.AddFactIdEquation(34, SpvOpConvertSToF, {23}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(32, {}),
-                                        MakeDataDescriptor(34, {})));
-}
-
-TEST(FactManagerTest, EquationAndEquivalenceFacts) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %12 "main"
-               OpExecutionMode %12 OriginUpperLeft
-               OpSource ESSL 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-         %15 = OpConstant %6 24
-         %16 = OpConstant %6 37
-         %12 = OpFunction %2 None %3
-         %13 = OpLabel
-         %14 = OpISub %6 %15 %16
-        %114 = OpCopyObject %6 %14
-         %17 = OpIAdd %6 %114 %16 ; ==> synonymous(%17, %15)
-         %18 = OpIAdd %6 %16 %114 ; ==> synonymous(%17, %18, %15)
-         %19 = OpISub %6 %114 %15
-        %119 = OpCopyObject %6 %19
-         %20 = OpSNegate %6 %119 ; ==> synonymous(%20, %16)
-         %21 = OpISub %6 %14 %19 ; ==> synonymous(%21, %15)
-         %22 = OpISub %6 %14 %18
-        %220 = OpCopyObject %6 %22
-         %23 = OpSNegate %6 %220 ; ==> synonymous(%23, %16)
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  fact_manager.AddFactIdEquation(14, SpvOpISub, {15, 16}, context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(114, {}),
-                                  MakeDataDescriptor(14, {}), context.get());
-  fact_manager.AddFactIdEquation(17, SpvOpIAdd, {114, 16}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
-                                        MakeDataDescriptor(15, {})));
-
-  fact_manager.AddFactIdEquation(18, SpvOpIAdd, {16, 114}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(18, {}),
-                                        MakeDataDescriptor(15, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(17, {}),
-                                        MakeDataDescriptor(18, {})));
-
-  fact_manager.AddFactIdEquation(19, SpvOpISub, {14, 15}, context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(119, {}),
-                                  MakeDataDescriptor(19, {}), context.get());
-  fact_manager.AddFactIdEquation(20, SpvOpSNegate, {119}, context.get());
-
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(20, {}),
-                                        MakeDataDescriptor(16, {})));
-
-  fact_manager.AddFactIdEquation(21, SpvOpISub, {14, 19}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                        MakeDataDescriptor(15, {})));
-
-  fact_manager.AddFactIdEquation(22, SpvOpISub, {14, 18}, context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(22, {}),
-                                  MakeDataDescriptor(220, {}), context.get());
-  fact_manager.AddFactIdEquation(23, SpvOpSNegate, {220}, context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(23, {}),
-                                        MakeDataDescriptor(16, {})));
-}
-
-TEST(FactManagerTest, CheckingFactsDoesNotAddConstants) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 320
-               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 Uniform %9
-         %11 = OpVariable %10 Uniform
-         %12 = OpConstant %6 0
-         %13 = OpTypePointer Uniform %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %14 = OpAccessChain %13 %11 %12
-         %15 = OpLoad %6 %14
-               OpStore %8 %15
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  // 8[0] == int(1)
-  ASSERT_TRUE(AddFactHelper(&fact_manager, context.get(), {1},
-                            MakeUniformBufferElementDescriptor(0, 0, {0})));
-
-  // Although 8[0] has the value 1, we do not have the constant 1 in the module.
-  // We thus should not find any constants available from uniforms for int type.
-  // Furthermore, the act of looking for appropriate constants should not change
-  // which constants are known to the constant manager.
-  auto int_type = context->get_type_mgr()->GetType(6)->AsInteger();
-  opt::analysis::IntConstant constant_one(int_type, {1});
-  ASSERT_FALSE(context->get_constant_mgr()->FindConstant(&constant_one));
-  auto available_constants =
-      fact_manager.GetConstantsAvailableFromUniformsForType(context.get(), 6);
-  ASSERT_EQ(0, available_constants.size());
-  ASSERT_TRUE(IsEqual(env, shader, context.get()));
-  ASSERT_FALSE(context->get_constant_mgr()->FindConstant(&constant_one));
-}
-
-TEST(FactManagerTest, IdIsIrrelevant) {
-  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
-         %12 = OpConstant %6 0
-         %13 = OpConstant %6 1
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-
-  const auto env = SPV_ENV_UNIVERSAL_1_3;
-  const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
-
-  ASSERT_FALSE(fact_manager.IdIsIrrelevant(12));
-  ASSERT_FALSE(fact_manager.IdIsIrrelevant(13));
-
-  fact_manager.AddFactIdIsIrrelevant(12);
-
-  ASSERT_TRUE(fact_manager.IdIsIrrelevant(12));
-  ASSERT_FALSE(fact_manager.IdIsIrrelevant(13));
-}
-
-}  // namespace
-}  // namespace fuzz
-}  // namespace spvtools
diff --git a/test/fuzz/fuzz_test_util.cpp b/test/fuzz/fuzz_test_util.cpp
index c717961..28d3f89 100644
--- a/test/fuzz/fuzz_test_util.cpp
+++ b/test/fuzz/fuzz_test_util.cpp
@@ -14,14 +14,40 @@
 
 #include "test/fuzz/fuzz_test_util.h"
 
+#include "gtest/gtest.h"
+
 #include <fstream>
 #include <iostream>
 
+#include "source/opt/def_use_manager.h"
 #include "tools/io.h"
 
 namespace spvtools {
 namespace fuzz {
 
+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;
+  }
+};
+
 bool IsEqual(const spv_target_env env,
              const std::vector<uint32_t>& expected_binary,
              const std::vector<uint32_t>& actual_binary) {
@@ -72,12 +98,11 @@
   return IsEqual(env, binary_1, binary_2);
 }
 
-bool IsValid(spv_target_env env, const opt::IRContext* ir) {
-  std::vector<uint32_t> binary;
-  ir->module()->ToBinary(&binary, false);
-  SpirvTools t(env);
-  t.SetMessageConsumer(kConsoleMessageConsumer);
-  return t.Validate(binary);
+bool IsEqual(const spv_target_env env, const std::vector<uint32_t>& binary_1,
+             const opt::IRContext* ir_2) {
+  std::vector<uint32_t> binary_2;
+  ir_2->module()->ToBinary(&binary_2, false);
+  return IsEqual(env, binary_1, binary_2);
 }
 
 std::string ToString(spv_target_env env, const opt::IRContext* ir) {
@@ -107,6 +132,15 @@
   }
 }
 
+void DumpTransformationsBinary(
+    const protobufs::TransformationSequence& transformations,
+    const char* filename) {
+  std::ofstream transformations_file;
+  transformations_file.open(filename, std::ios::out | std::ios::binary);
+  transformations.SerializeToOstream(&transformations_file);
+  transformations_file.close();
+}
+
 void DumpTransformationsJson(
     const protobufs::TransformationSequence& transformations,
     const char* filename) {
@@ -122,5 +156,33 @@
   }
 }
 
+void ApplyAndCheckFreshIds(
+    const Transformation& transformation, opt::IRContext* ir_context,
+    TransformationContext* transformation_context,
+    const std::unordered_set<uint32_t>& issued_overflow_ids) {
+  opt::analysis::DefUseManager::IdToDefMap before_transformation =
+      ir_context->get_def_use_mgr()->id_to_defs();
+  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();
+  for (auto& entry : after_transformation) {
+    uint32_t id = entry.first;
+    bool introduced_by_transformation_message =
+        fresh_ids_for_transformation.count(id);
+    bool introduced_by_overflow_ids = issued_overflow_ids.count(id);
+    ASSERT_FALSE(introduced_by_transformation_message &&
+                 introduced_by_overflow_ids);
+    if (before_transformation.count(entry.first)) {
+      ASSERT_FALSE(introduced_by_transformation_message ||
+                   introduced_by_overflow_ids);
+    } else {
+      ASSERT_TRUE(introduced_by_transformation_message ||
+                  introduced_by_overflow_ids);
+    }
+  }
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/fuzz_test_util.h b/test/fuzz/fuzz_test_util.h
index 9e08bf6..1ee0875 100644
--- a/test/fuzz/fuzz_test_util.h
+++ b/test/fuzz/fuzz_test_util.h
@@ -15,11 +15,11 @@
 #ifndef TEST_FUZZ_FUZZ_TEST_UTIL_H_
 #define TEST_FUZZ_FUZZ_TEST_UTIL_H_
 
-#include "gtest/gtest.h"
-
 #include <vector>
 
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/fuzz/transformation_context.h"
 #include "source/opt/build_module.h"
 #include "source/opt/ir_context.h"
 #include "spirv-tools/libspirv.h"
@@ -27,6 +27,8 @@
 namespace spvtools {
 namespace fuzz {
 
+extern const spvtools::MessageConsumer kConsoleMessageConsumer;
+
 // Returns true if and only if the given binaries are bit-wise equal.
 bool IsEqual(spv_target_env env, const std::vector<uint32_t>& expected_binary,
              const std::vector<uint32_t>& actual_binary);
@@ -46,9 +48,10 @@
 bool IsEqual(spv_target_env env, const opt::IRContext* ir_1,
              const opt::IRContext* ir_2);
 
-// Assembles the given IR context and returns true if and only if
-// the resulting binary is valid.
-bool IsValid(spv_target_env env, const opt::IRContext* ir);
+// Turns |ir_2| into a binary, then returns true if and only if the resulting
+// binary is bit-wise equal to |binary_1|.
+bool IsEqual(spv_target_env env, const std::vector<uint32_t>& binary_1,
+             const opt::IRContext* ir_2);
 
 // Assembles the given IR context, then returns its disassembly as a string.
 // Useful for debugging.
@@ -66,34 +69,6 @@
 const uint32_t kFuzzDisassembleOption =
     SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT;
 
-// A silent message consumer.
-const spvtools::MessageConsumer kSilentConsumer =
-    [](spv_message_level_t, const char*, const spv_position_t&,
-       const char*) -> void {};
-
-const spvtools::MessageConsumer kConsoleMessageConsumer =
-    [](spv_message_level_t level, const char*, const spv_position_t& position,
-       const char* message) -> void {
-  switch (level) {
-    case SPV_MSG_FATAL:
-    case SPV_MSG_INTERNAL_ERROR:
-    case SPV_MSG_ERROR:
-      std::cerr << "error: line " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_WARNING:
-      std::cout << "warning: line " << position.index << ": " << message
-                << std::endl;
-      break;
-    case SPV_MSG_INFO:
-      std::cout << "info: line " << position.index << ": " << message
-                << std::endl;
-      break;
-    default:
-      break;
-  }
-};
-
 // Dumps the SPIRV-V module in |context| to file |filename|. Useful for
 // interactive debugging.
 void DumpShader(opt::IRContext* context, const char* filename);
@@ -101,12 +76,27 @@
 // Dumps |binary| to file |filename|. Useful for interactive debugging.
 void DumpShader(const std::vector<uint32_t>& binary, const char* filename);
 
+// Dumps |transformations| to file |filename| in binary format. Useful for
+// interactive debugging.
+void DumpTransformationsBinary(
+    const protobufs::TransformationSequence& transformations,
+    const char* filename);
+
 // Dumps |transformations| to file |filename| in JSON format. Useful for
 // interactive debugging.
 void DumpTransformationsJson(
     const protobufs::TransformationSequence& transformations,
     const char* filename);
 
+// Applies |transformation| to |ir_context| and |transformation_context|, and
+// asserts that any ids in |ir_context| that are only present post-
+// transformation are either contained in |transformation.GetFreshIds()|, or
+// in |issued_overflow_ids|.
+void ApplyAndCheckFreshIds(
+    const Transformation& transformation, opt::IRContext* ir_context,
+    TransformationContext* transformation_context,
+    const std::unordered_set<uint32_t>& issued_overflow_ids = {{}});
+
 }  // namespace fuzz
 }  // namespace spvtools
 
diff --git a/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
new file mode 100644
index 0000000..f7a0996
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_add_opphi_synonyms_test.cpp
@@ -0,0 +1,176 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_opphi_synonyms.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) {
+  protobufs::FactDataSynonym data_synonym_fact;
+  *data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {});
+  *data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {});
+  protobufs::Fact result;
+  *result.mutable_data_synonym_fact() = data_synonym_fact;
+  return result;
+}
+
+// Adds synonym facts to the fact manager.
+void SetUpIdSynonyms(FactManager* fact_manager) {
+  // Synonyms {9, 11, 15, 16, 21, 22}
+  fact_manager->MaybeAddFact(MakeSynonymFact(11, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(15, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(16, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(21, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(22, 9));
+
+  // Synonyms {10, 23}
+  fact_manager->MaybeAddFact(MakeSynonymFact(10, 23));
+
+  // Synonyms {14, 27}
+  fact_manager->MaybeAddFact(MakeSynonymFact(14, 27));
+
+  // Synonyms {24, 26, 30}
+  fact_manager->MaybeAddFact(MakeSynonymFact(26, 24));
+  fact_manager->MaybeAddFact(MakeSynonymFact(30, 24));
+}
+
+// Returns true if the given lists have the same elements, regardless of their
+// order.
+template <typename T>
+bool ListsHaveTheSameElements(const std::vector<T>& list1,
+                              const std::vector<T>& list2) {
+  auto sorted1 = list1;
+  std::sort(sorted1.begin(), sorted1.end());
+
+  auto sorted2 = list2;
+  std::sort(sorted2.begin(), sorted2.end());
+
+  return sorted1 == sorted2;
+}
+
+std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+         %31 = OpTypeFunction %7
+          %8 = OpTypeInt 32 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %8 1
+         %12 = OpTypePointer Function %7
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+         %14 = OpVariable %12 Function
+         %15 = OpCopyObject %7 %9
+         %16 = OpCopyObject %8 %11
+               OpBranch %17
+         %17 = OpLabel
+               OpSelectionMerge %18 None
+               OpBranchConditional %6 %19 %20
+         %19 = OpLabel
+         %21 = OpCopyObject %7 %15
+         %22 = OpCopyObject %8 %16
+         %23 = OpCopyObject %7 %10
+         %24 = OpIAdd %7 %9 %10
+               OpBranch %18
+         %20 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %26 = OpIAdd %7 %15 %10
+         %27 = OpCopyObject %12 %14
+               OpSelectionMerge %28 None
+               OpBranchConditional %6 %29 %28
+         %29 = OpLabel
+         %30 = OpCopyObject %7 %26
+               OpBranch %28
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %32 = OpFunction %7 None %31
+         %33 = OpLabel
+               OpReturnValue %9
+               OpFunctionEnd
+)";
+
+TEST(FuzzerPassAddOpPhiSynonymsTest, HelperFunctions) {
+  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);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassAddOpPhiSynonyms fuzzer_pass(context.get(), &transformation_context,
+                                         &fuzzer_context,
+                                         &transformation_sequence);
+
+  SetUpIdSynonyms(transformation_context.GetFactManager());
+
+  std::vector<std::set<uint32_t>> expected_equivalence_classes = {
+      {9, 15, 21}, {11, 16, 22}, {10, 23}, {6}, {24, 26, 30}};
+
+  ASSERT_TRUE(ListsHaveTheSameElements<std::set<uint32_t>>(
+      fuzzer_pass.GetIdEquivalenceClasses(), expected_equivalence_classes));
+
+  // The set {24, 26, 30} is not suitable for 18 (none if the ids is available
+  // for predecessor 20).
+  ASSERT_FALSE(
+      fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 18, 1));
+
+  // The set {6} is not suitable for 18 if we require at least 2 distinct
+  // available ids.
+  ASSERT_FALSE(fuzzer_pass.EquivalenceClassIsSuitableForBlock({6}, 18, 2));
+
+  // Only id 26 from the set {24, 26, 30} is available to use for the
+  // transformation at block 29, so the set is not suitable if we want at least
+  // 2 available ids.
+  ASSERT_FALSE(
+      fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 2));
+
+  ASSERT_TRUE(
+      fuzzer_pass.EquivalenceClassIsSuitableForBlock({24, 26, 30}, 29, 1));
+
+  // %21 is not available at the end of block 20.
+  ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>(
+      fuzzer_pass.GetSuitableIds({9, 15, 21}, 20), {9, 15}));
+
+  // %24 and %30 are not available at the end of block 18.
+  ASSERT_TRUE(ListsHaveTheSameElements<uint32_t>(
+      fuzzer_pass.GetSuitableIds({24, 26, 30}, 18), {26}));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzzer_pass_construct_composites_test.cpp b/test/fuzz/fuzzer_pass_construct_composites_test.cpp
index cc21f74..d49d1d6 100644
--- a/test/fuzz/fuzzer_pass_construct_composites_test.cpp
+++ b/test/fuzz/fuzzer_pass_construct_composites_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/fuzzer_pass_construct_composites.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/pseudo_random_generator.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -79,13 +82,11 @@
   for (uint32_t i = 0; i < 10; i++) {
     const auto context =
         BuildModule(env, consumer, shader, kFuzzAssembleOption);
-    ASSERT_TRUE(IsValid(env, context.get()));
-
-    FactManager fact_manager;
     spvtools::ValidatorOptions validator_options;
-    TransformationContext transformation_context(&fact_manager,
-                                                 validator_options);
-
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
     FuzzerContext fuzzer_context(prng.get(), 100);
     protobufs::TransformationSequence transformation_sequence;
 
@@ -96,7 +97,8 @@
     fuzzer_pass.Apply();
 
     // We just check that the result is valid.
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 }
 
@@ -161,13 +163,11 @@
   for (uint32_t i = 0; i < 10; i++) {
     const auto context =
         BuildModule(env, consumer, shader, kFuzzAssembleOption);
-    ASSERT_TRUE(IsValid(env, context.get()));
-
-    FactManager fact_manager;
     spvtools::ValidatorOptions validator_options;
-    TransformationContext transformation_context(&fact_manager,
-                                                 validator_options);
-
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
     FuzzerContext fuzzer_context(prng.get(), 100);
     protobufs::TransformationSequence transformation_sequence;
 
@@ -178,7 +178,8 @@
     fuzzer_pass.Apply();
 
     // We just check that the result is valid.
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 }
 
diff --git a/test/fuzz/fuzzer_pass_donate_modules_test.cpp b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
index 0be3d5a..1a7cd4a 100644
--- a/test/fuzz/fuzzer_pass_donate_modules_test.cpp
+++ b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/fuzz/fuzzer_pass_donate_modules.h"
+
 #include <algorithm>
 
-#include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "gtest/gtest.h"
 #include "source/fuzz/pseudo_random_generator.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -187,18 +189,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -212,7 +216,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonationWithUniforms) {
@@ -265,18 +270,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context = BuildModule(
       env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context = BuildModule(
       env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -288,7 +295,8 @@
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -393,18 +401,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context = BuildModule(
       env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context = BuildModule(
       env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -416,7 +426,8 @@
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -485,18 +496,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context = BuildModule(
       env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context = BuildModule(
       env, consumer, recipient_and_donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -510,7 +523,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateOpConstantNull) {
@@ -552,18 +566,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -577,7 +593,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImages) {
@@ -677,18 +694,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -702,7 +721,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesSampler) {
@@ -770,18 +790,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -795,7 +817,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageStructField) {
@@ -899,18 +922,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -924,7 +949,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateCodeThatUsesImageFunctionParameter) {
@@ -1032,18 +1058,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -1057,7 +1085,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateShaderWithImageStorageClass) {
@@ -1111,18 +1140,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -1136,7 +1167,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArray) {
@@ -1195,18 +1227,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -1220,7 +1254,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithRuntimeArrayLivesafe) {
@@ -1296,18 +1331,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -1321,7 +1358,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithWorkgroupVariables) {
@@ -1365,18 +1403,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -1390,7 +1430,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, DonateComputeShaderWithAtomics) {
@@ -1472,18 +1513,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -1497,7 +1540,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, Miscellaneous1) {
@@ -1653,18 +1697,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator rng(0);
   FuzzerContext fuzzer_context(&rng, 100);
@@ -1678,7 +1724,8 @@
 
   // We just check that the result is valid.  Checking to what it should be
   // exactly equal to would be very fragile.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 TEST(FuzzerPassDonateModulesTest, OpSpecConstantInstructions) {
@@ -1722,18 +1769,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator prng(0);
   FuzzerContext fuzzer_context(&prng, 100);
@@ -1746,7 +1795,8 @@
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
   // Check that the module is valid first.
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -1876,18 +1926,20 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_0;
   const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
   const auto recipient_context =
       BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 
   const auto donor_context =
       BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, donor_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
 
   PseudoRandomGenerator rng(0);
   FuzzerContext fuzzer_context(&rng, 100);
@@ -1899,7 +1951,316 @@
 
   fuzzer_pass.DonateSingleModule(donor_context.get(), false);
 
-  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
+}
+
+TEST(FuzzerPassDonateModulesTest, HandlesCapabilities) {
+  std::string donor_shader = R"(
+               OpCapability VariablePointersStorageBuffer
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %11 = OpConstant %6 23
+          %7 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %9
+
+          %9 = OpLabel
+         %10 = OpPhi %7 %8 %5
+               OpStore %10 %11
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  std::string recipient_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
+
+  PseudoRandomGenerator rng(0);
+  FuzzerContext fuzzer_context(&rng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+                                      &transformation_context, &fuzzer_context,
+                                      &transformation_sequence, {});
+
+  ASSERT_TRUE(donor_context->get_feature_mgr()->HasCapability(
+      SpvCapabilityVariablePointersStorageBuffer));
+  ASSERT_FALSE(recipient_context->get_feature_mgr()->HasCapability(
+      SpvCapabilityVariablePointersStorageBuffer));
+
+  fuzzer_pass.DonateSingleModule(donor_context.get(), false);
+
+  // Check that recipient module hasn't changed.
+  ASSERT_TRUE(IsEqual(env, recipient_shader, recipient_context.get()));
+
+  // Add the missing capability.
+  //
+  // We are adding VariablePointers to test the case when donor and recipient
+  // have different OpCapability instructions but the same capabilities. In our
+  // example, VariablePointers implicitly declares
+  // VariablePointersStorageBuffer. Thus, two modules must be compatible.
+  recipient_context->AddCapability(SpvCapabilityVariablePointers);
+
+  ASSERT_TRUE(donor_context->get_feature_mgr()->HasCapability(
+      SpvCapabilityVariablePointersStorageBuffer));
+  ASSERT_TRUE(recipient_context->get_feature_mgr()->HasCapability(
+      SpvCapabilityVariablePointersStorageBuffer));
+
+  fuzzer_pass.DonateSingleModule(donor_context.get(), false);
+
+  // Check that donation was successful.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+        %100 = OpTypeFloat 32
+        %101 = OpConstant %100 23
+        %102 = OpTypePointer Function %100
+        %105 = OpConstant %100 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        %103 = OpFunction %2 None %3
+        %104 = OpLabel
+        %106 = OpVariable %102 Function %105
+               OpBranch %107
+        %107 = OpLabel
+        %108 = OpPhi %102 %106 %104
+               OpStore %108 %101
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, recipient_context.get()));
+}
+
+TEST(FuzzerPassDonateModulesTest, HandlesOpPhisInMergeBlock) {
+  std::string donor_shader = R"(
+               ; OpPhis don't support pointers without this capability
+               ; and we need pointers to test some of the functionality
+               OpCapability VariablePointers
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %14 = OpTypeBool
+         %15 = OpConstantTrue %14
+         %42 = OpTypePointer Function %14
+
+          ; back-edge block is unreachable in the CFG
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpLoopMerge %8 %7 None
+               OpBranch %8
+          %7 = OpLabel
+               OpBranch %6
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+          ; back-edge block already has an edge to the merge block
+          %9 = OpFunction %2 None %3
+         %10 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %13 %12 None
+               OpBranch %12
+         %12 = OpLabel
+               OpBranchConditional %15 %11 %13
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         ; merge block has no OpPhis
+         %16 = OpFunction %2 None %3
+         %17 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpLoopMerge %20 %19 None
+               OpBranchConditional %15 %19 %20
+         %19 = OpLabel
+               OpBranch %18
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         ; merge block has OpPhis and some of their operands are available at
+         ; the back-edge block
+         %21 = OpFunction %2 None %3
+         %22 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+         %24 = OpCopyObject %14 %15
+               OpLoopMerge %28 %27 None
+               OpBranchConditional %15 %25 %28
+         %25 = OpLabel
+         %26 = OpCopyObject %14 %15
+               OpBranchConditional %15 %28 %27
+         %27 = OpLabel
+               OpBranch %23
+         %28 = OpLabel
+         %29 = OpPhi %14 %24 %23 %26 %25
+               OpReturn
+               OpFunctionEnd
+
+         ; none of the OpPhis' operands dominate the back-edge block but some of
+         ; them have basic type
+         %30 = OpFunction %2 None %3
+         %31 = OpLabel
+               OpBranch %32
+         %32 = OpLabel
+               OpLoopMerge %40 %39 None
+               OpBranch %33
+         %33 = OpLabel
+               OpSelectionMerge %38 None
+               OpBranchConditional %15 %34 %36
+         %34 = OpLabel
+         %35 = OpCopyObject %14 %15
+               OpBranchConditional %35 %38 %40
+         %36 = OpLabel
+         %37 = OpCopyObject %14 %15
+               OpBranchConditional %37 %38 %40
+         %38 = OpLabel
+               OpBranch %39
+         %39 = OpLabel
+               OpBranch %32
+         %40 = OpLabel
+         %41 = OpPhi %14 %35 %34 %37 %36
+               OpReturn
+               OpFunctionEnd
+
+         ; none of the OpPhis' operands dominate the back-edge block and none of
+         ; them have basic type
+         %43 = OpFunction %2 None %3
+         %44 = OpLabel
+         %45 = OpVariable %42 Function
+               OpBranch %46
+         %46 = OpLabel
+               OpLoopMerge %54 %53 None
+               OpBranch %47
+         %47 = OpLabel
+               OpSelectionMerge %52 None
+               OpBranchConditional %15 %48 %50
+         %48 = OpLabel
+         %49 = OpCopyObject %42 %45
+               OpBranchConditional %15 %52 %54
+         %50 = OpLabel
+         %51 = OpCopyObject %42 %45
+               OpBranchConditional %15 %52 %54
+         %52 = OpLabel
+               OpBranch %53
+         %53 = OpLabel
+               OpBranch %46
+         %54 = OpLabel
+         %55 = OpPhi %42 %49 %48 %51 %50
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string recipient_shader = R"(
+               ; OpPhis don't support pointers without this capability
+               ; and we need pointers to test some of the functionality
+               OpCapability VariablePointers
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  spvtools::ValidatorOptions validator_options;
+
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_context.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(recipient_context.get()), validator_options);
+
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+                                      &transformation_context, &fuzzer_context,
+                                      &transformation_sequence, {});
+
+  fuzzer_pass.DonateSingleModule(donor_context.get(), true);
+
+  // We just check that the result is valid. Checking to what it should be
+  // exactly equal to would be very fragile.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      recipient_context.get(), validator_options, kConsoleMessageConsumer));
 }
 
 }  // namespace
diff --git a/test/fuzz/fuzzer_pass_outline_functions_test.cpp b/test/fuzz/fuzzer_pass_outline_functions_test.cpp
new file mode 100644
index 0000000..576962c
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_outline_functions_test.cpp
@@ -0,0 +1,597 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_outline_functions.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "a"
+               OpName %4 "b"
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+               OpDecorate %5 RelaxedPrecision
+               OpDecorate %6 RelaxedPrecision
+               OpDecorate %7 RelaxedPrecision
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+         %10 = OpTypeVoid
+         %11 = OpTypeFunction %10
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %14 = OpConstant %12 8
+         %15 = OpConstant %12 23
+         %16 = OpTypeBool
+         %17 = OpConstantTrue %16
+         %18 = OpConstant %12 0
+         %19 = OpConstant %12 1
+          %2 = OpFunction %10 None %11
+         %20 = OpLabel
+          %3 = OpVariable %13 Function
+          %4 = OpVariable %13 Function
+               OpStore %3 %14
+               OpStore %4 %15
+               OpBranch %21
+         %21 = OpLabel
+               OpLoopMerge %22 %23 None
+               OpBranch %24
+         %24 = OpLabel
+         %25 = OpPhi %12 %19 %21 %18 %26
+               OpLoopMerge %27 %26 None
+               OpBranch %28
+         %28 = OpLabel
+          %5 = OpLoad %12 %3
+         %29 = OpSGreaterThan %16 %5 %18
+               OpBranchConditional %29 %30 %27
+         %30 = OpLabel
+          %6 = OpLoad %12 %4
+          %7 = OpISub %12 %6 %19
+               OpStore %4 %7
+               OpBranch %26
+         %26 = OpLabel
+          %8 = OpLoad %12 %3
+          %9 = OpISub %12 %8 %19
+               OpStore %3 %9
+               OpBranch %24
+         %27 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+               OpBranch %21
+         %22 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+               OpLoopMerge %32 %31 None
+               OpBranchConditional %17 %31 %32
+         %32 = OpLabel
+               OpSelectionMerge %33 None
+               OpBranchConditional %17 %34 %35
+         %34 = OpLabel
+               OpBranch %33
+         %35 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+         %42 = OpPhi %12 %19 %33 %18 %34 %18 %35
+               OpLoopMerge %36 %33 None
+               OpBranchConditional %17 %36 %33
+         %36 = OpLabel
+         %43 = OpPhi %12 %18 %33 %18 %41
+               OpReturn
+         %37 = OpLabel
+               OpLoopMerge %38 %39 None
+               OpBranch %40
+         %40 = OpLabel
+               OpBranchConditional %17 %41 %38
+         %41 = OpLabel
+               OpBranchConditional %17 %36 %39
+         %39 = OpLabel
+               OpBranch %37
+         %38 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(FuzzerPassOutlineFunctionsTest, EntryIsAlreadySuitable) {
+  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);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
+                                         &fuzzer_context,
+                                         &transformation_sequence);
+
+  // Block 28
+  auto suitable_entry_block =
+      fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+          context->get_instr_block(28));
+
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 28);
+
+  // Block 32
+  suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+      context->get_instr_block(32));
+
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 32);
+
+  // Block 41
+  suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+      context->get_instr_block(41));
+
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 41);
+
+  // The module should not have been changed.
+  ASSERT_TRUE(IsEqual(env, shader, context.get()));
+}
+
+TEST(FuzzerPassOutlineFunctionsTest, EntryHasOpVariable) {
+  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);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
+                                         &fuzzer_context,
+                                         &transformation_sequence);
+
+  // Block 20
+  auto suitable_entry_block =
+      fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+          context->get_instr_block(20));
+
+  // The block should have been split, the new entry block being the block
+  // generated by the splitting.
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 100);
+
+  std::string after_adjustment = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "a"
+               OpName %4 "b"
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+               OpDecorate %5 RelaxedPrecision
+               OpDecorate %6 RelaxedPrecision
+               OpDecorate %7 RelaxedPrecision
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+         %10 = OpTypeVoid
+         %11 = OpTypeFunction %10
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %14 = OpConstant %12 8
+         %15 = OpConstant %12 23
+         %16 = OpTypeBool
+         %17 = OpConstantTrue %16
+         %18 = OpConstant %12 0
+         %19 = OpConstant %12 1
+          %2 = OpFunction %10 None %11
+         %20 = OpLabel
+          %3 = OpVariable %13 Function
+          %4 = OpVariable %13 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %3 %14
+               OpStore %4 %15
+               OpBranch %21
+         %21 = OpLabel
+               OpLoopMerge %22 %23 None
+               OpBranch %24
+         %24 = OpLabel
+         %25 = OpPhi %12 %19 %21 %18 %26
+               OpLoopMerge %27 %26 None
+               OpBranch %28
+         %28 = OpLabel
+          %5 = OpLoad %12 %3
+         %29 = OpSGreaterThan %16 %5 %18
+               OpBranchConditional %29 %30 %27
+         %30 = OpLabel
+          %6 = OpLoad %12 %4
+          %7 = OpISub %12 %6 %19
+               OpStore %4 %7
+               OpBranch %26
+         %26 = OpLabel
+          %8 = OpLoad %12 %3
+          %9 = OpISub %12 %8 %19
+               OpStore %3 %9
+               OpBranch %24
+         %27 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+               OpBranch %21
+         %22 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+               OpLoopMerge %32 %31 None
+               OpBranchConditional %17 %31 %32
+         %32 = OpLabel
+               OpSelectionMerge %33 None
+               OpBranchConditional %17 %34 %35
+         %34 = OpLabel
+               OpBranch %33
+         %35 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+         %42 = OpPhi %12 %19 %33 %18 %34 %18 %35
+               OpLoopMerge %36 %33 None
+               OpBranchConditional %17 %36 %33
+         %36 = OpLabel
+         %43 = OpPhi %12 %18 %33 %18 %41
+               OpReturn
+         %37 = OpLabel
+               OpLoopMerge %38 %39 None
+               OpBranch %40
+         %40 = OpLabel
+               OpBranchConditional %17 %41 %38
+         %41 = OpLabel
+               OpBranchConditional %17 %36 %39
+         %39 = OpLabel
+               OpBranch %37
+         %38 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_adjustment, context.get()));
+}
+
+TEST(FuzzerPassOutlineFunctionsTest, EntryBlockIsHeader) {
+  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);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
+                                         &fuzzer_context,
+                                         &transformation_sequence);
+
+  // Block 21
+  auto suitable_entry_block =
+      fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+          context->get_instr_block(21));
+
+  // A suitable entry block should have been found by finding the preheader
+  // (%20) and then splitting it.
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 100);
+
+  // Block 24
+  suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+      context->get_instr_block(24));
+
+  // A preheader should have been created, because the current one is a loop
+  // header.
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 101);
+
+  // Block 31
+  suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+      context->get_instr_block(31));
+
+  // An existing suitable entry block should have been found by finding the
+  // preheader (%22), which is already suitable.
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 22);
+
+  // Block 33
+  suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+      context->get_instr_block(33));
+
+  // An existing suitable entry block should have been found by creating a new
+  // preheader (there is not one already), and then splitting it (as it contains
+  // OpPhi).
+  ASSERT_TRUE(suitable_entry_block);
+  ASSERT_TRUE(suitable_entry_block->GetLabel()->result_id() == 104);
+
+  // Block 37
+  suitable_entry_block = fuzzer_pass.MaybeGetEntryBlockSuitableForOutlining(
+      context->get_instr_block(37));
+
+  // No suitable entry block can be found for block 37, since it is a loop
+  // header with only one predecessor (the back-edge block).
+  ASSERT_FALSE(suitable_entry_block);
+
+  std::string after_adjustments = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "a"
+               OpName %4 "b"
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+               OpDecorate %5 RelaxedPrecision
+               OpDecorate %6 RelaxedPrecision
+               OpDecorate %7 RelaxedPrecision
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+         %10 = OpTypeVoid
+         %11 = OpTypeFunction %10
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %14 = OpConstant %12 8
+         %15 = OpConstant %12 23
+         %16 = OpTypeBool
+         %17 = OpConstantTrue %16
+         %18 = OpConstant %12 0
+         %19 = OpConstant %12 1
+          %2 = OpFunction %10 None %11
+         %20 = OpLabel
+          %3 = OpVariable %13 Function
+          %4 = OpVariable %13 Function
+               OpBranch %100
+        %100 = OpLabel
+               OpStore %3 %14
+               OpStore %4 %15
+               OpBranch %21
+         %21 = OpLabel
+               OpLoopMerge %22 %23 None
+               OpBranch %101
+        %101 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+         %25 = OpPhi %12 %19 %101 %18 %26
+               OpLoopMerge %27 %26 None
+               OpBranch %28
+         %28 = OpLabel
+          %5 = OpLoad %12 %3
+         %29 = OpSGreaterThan %16 %5 %18
+               OpBranchConditional %29 %30 %27
+         %30 = OpLabel
+          %6 = OpLoad %12 %4
+          %7 = OpISub %12 %6 %19
+               OpStore %4 %7
+               OpBranch %26
+         %26 = OpLabel
+          %8 = OpLoad %12 %3
+          %9 = OpISub %12 %8 %19
+               OpStore %3 %9
+               OpBranch %24
+         %27 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+               OpBranch %21
+         %22 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+               OpLoopMerge %32 %31 None
+               OpBranchConditional %17 %31 %32
+         %32 = OpLabel
+               OpSelectionMerge %102 None
+               OpBranchConditional %17 %34 %35
+         %34 = OpLabel
+               OpBranch %102
+         %35 = OpLabel
+               OpBranch %102
+        %102 = OpLabel
+        %103 = OpPhi %12 %18 %34 %18 %35
+               OpBranch %104
+        %104 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+         %42 = OpPhi %12 %103 %104 %19 %33
+               OpLoopMerge %36 %33 None
+               OpBranchConditional %17 %36 %33
+         %36 = OpLabel
+         %43 = OpPhi %12 %18 %33 %18 %41
+               OpReturn
+         %37 = OpLabel
+               OpLoopMerge %38 %39 None
+               OpBranch %40
+         %40 = OpLabel
+               OpBranchConditional %17 %41 %38
+         %41 = OpLabel
+               OpBranchConditional %17 %36 %39
+         %39 = OpLabel
+               OpBranch %37
+         %38 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_adjustments, context.get()));
+}
+
+TEST(FuzzerPassOutlineFunctionsTest, ExitBlock) {
+  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);
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassOutlineFunctions fuzzer_pass(context.get(), &transformation_context,
+                                         &fuzzer_context,
+                                         &transformation_sequence);
+
+  // Block 39 is not a merge block, so it is already suitable.
+  auto suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining(
+      context->get_instr_block(39));
+  ASSERT_TRUE(suitable_exit_block);
+  ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 39);
+
+  // The following are merge blocks and, thus, they will need to be split.
+
+  // Block 22
+  suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining(
+      context->get_instr_block(22));
+  ASSERT_TRUE(suitable_exit_block);
+  ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 100);
+
+  // Block 27
+  suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining(
+      context->get_instr_block(27));
+  ASSERT_TRUE(suitable_exit_block);
+  ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 101);
+
+  // Block 36
+  suitable_exit_block = fuzzer_pass.MaybeGetExitBlockSuitableForOutlining(
+      context->get_instr_block(36));
+  ASSERT_TRUE(suitable_exit_block);
+  ASSERT_TRUE(suitable_exit_block->GetLabel()->result_id() == 102);
+
+  std::string after_adjustments = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "a"
+               OpName %4 "b"
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+               OpDecorate %5 RelaxedPrecision
+               OpDecorate %6 RelaxedPrecision
+               OpDecorate %7 RelaxedPrecision
+               OpDecorate %8 RelaxedPrecision
+               OpDecorate %9 RelaxedPrecision
+         %10 = OpTypeVoid
+         %11 = OpTypeFunction %10
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %14 = OpConstant %12 8
+         %15 = OpConstant %12 23
+         %16 = OpTypeBool
+         %17 = OpConstantTrue %16
+         %18 = OpConstant %12 0
+         %19 = OpConstant %12 1
+          %2 = OpFunction %10 None %11
+         %20 = OpLabel
+          %3 = OpVariable %13 Function
+          %4 = OpVariable %13 Function
+               OpStore %3 %14
+               OpStore %4 %15
+               OpBranch %21
+         %21 = OpLabel
+               OpLoopMerge %22 %23 None
+               OpBranch %24
+         %24 = OpLabel
+         %25 = OpPhi %12 %19 %21 %18 %26
+               OpLoopMerge %27 %26 None
+               OpBranch %28
+         %28 = OpLabel
+          %5 = OpLoad %12 %3
+         %29 = OpSGreaterThan %16 %5 %18
+               OpBranchConditional %29 %30 %27
+         %30 = OpLabel
+          %6 = OpLoad %12 %4
+          %7 = OpISub %12 %6 %19
+               OpStore %4 %7
+               OpBranch %26
+         %26 = OpLabel
+          %8 = OpLoad %12 %3
+          %9 = OpISub %12 %8 %19
+               OpStore %3 %9
+               OpBranch %24
+         %27 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+               OpBranch %21
+         %22 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+               OpLoopMerge %32 %31 None
+               OpBranchConditional %17 %31 %32
+         %32 = OpLabel
+               OpSelectionMerge %33 None
+               OpBranchConditional %17 %34 %35
+         %34 = OpLabel
+               OpBranch %33
+         %35 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+         %42 = OpPhi %12 %19 %33 %18 %34 %18 %35
+               OpLoopMerge %36 %33 None
+               OpBranchConditional %17 %36 %33
+         %36 = OpLabel
+         %43 = OpPhi %12 %18 %33 %18 %41
+               OpBranch %102
+        %102 = OpLabel
+               OpReturn
+         %37 = OpLabel
+               OpLoopMerge %38 %39 None
+               OpBranch %40
+         %40 = OpLabel
+               OpBranchConditional %17 %41 %38
+         %41 = OpLabel
+               OpBranchConditional %17 %36 %39
+         %39 = OpLabel
+               OpBranch %37
+         %38 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_adjustments, context.get()));
+}
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzzer_pass_test.cpp b/test/fuzz/fuzzer_pass_test.cpp
new file mode 100644
index 0000000..283aa11
--- /dev/null
+++ b/test/fuzz/fuzzer_pass_test.cpp
@@ -0,0 +1,103 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_pass_add_opphi_synonyms.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+class FuzzerPassMock : public FuzzerPass {
+ public:
+  FuzzerPassMock(opt::IRContext* ir_context,
+                 TransformationContext* transformation_context,
+                 FuzzerContext* fuzzer_context,
+                 protobufs::TransformationSequence* transformations)
+      : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                   transformations) {}
+
+  ~FuzzerPassMock() override = default;
+
+  const std::unordered_set<uint32_t>& GetReachedInstructions() const {
+    return reached_ids_;
+  }
+
+  void Apply() override {
+    ForEachInstructionWithInstructionDescriptor(
+        [this](opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
+               opt::BasicBlock::iterator inst_it,
+               const protobufs::InstructionDescriptor& /*unused*/) {
+          if (inst_it->result_id()) {
+            reached_ids_.insert(inst_it->result_id());
+          }
+        });
+  }
+
+ private:
+  std::unordered_set<uint32_t> reached_ids_;
+};
+
+TEST(FuzzerPassTest, ForEachInstructionWithInstructionDescriptor) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %7 = OpUndef %6
+               OpReturn
+          %8 = OpLabel
+          %9 = OpUndef %6
+               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);
+  // Check that %5 is reachable and %8 is unreachable as expected.
+  const auto* dominator_analysis =
+      context->GetDominatorAnalysis(context->GetFunction(4));
+  ASSERT_TRUE(dominator_analysis->IsReachable(5));
+  ASSERT_FALSE(dominator_analysis->IsReachable(8));
+
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformations;
+  FuzzerPassMock fuzzer_pass_mock(context.get(), &transformation_context,
+                                  &fuzzer_context, &transformations);
+  fuzzer_pass_mock.Apply();
+
+  ASSERT_TRUE(fuzzer_pass_mock.GetReachedInstructions().count(7));
+  ASSERT_FALSE(fuzzer_pass_mock.GetReachedInstructions().count(9));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index 8a23574..dc90574 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -13,8 +13,11 @@
 // limitations under the License.
 
 #include "source/fuzz/fuzzer.h"
-#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/replayer.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/pseudo_random_generator.h"
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -1587,7 +1590,7 @@
           %2 = OpFunction %132 None %133
         %164 = OpLabel
         %184 = OpLoad %15 %40
-	%213 = OpLoad %38 %41
+        %213 = OpLoad %38 %41
         %216 = OpSampledImage %45 %184 %213
         %217 = OpImageSampleImplicitLod %76 %216 %112 Bias %55
                OpReturn
@@ -1639,43 +1642,53 @@
     });
   }
 
+  std::vector<Fuzzer::RepeatedPassStrategy> strategies{
+      Fuzzer::RepeatedPassStrategy::kSimple,
+      Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations,
+      Fuzzer::RepeatedPassStrategy::kRandomWithRecommendations};
+  uint32_t strategy_index = 0;
   for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) {
-    std::vector<uint32_t> fuzzer_binary_out;
-    protobufs::TransformationSequence fuzzer_transformation_sequence_out;
-
     spvtools::ValidatorOptions validator_options;
-    Fuzzer fuzzer(env, seed, true, validator_options);
-    fuzzer.SetMessageConsumer(kConsoleMessageConsumer);
-    auto fuzzer_result_status =
-        fuzzer.Run(binary_in, initial_facts, donor_suppliers,
-                   &fuzzer_binary_out, &fuzzer_transformation_sequence_out);
-    ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
-    ASSERT_TRUE(t.Validate(fuzzer_binary_out));
+    // 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();
 
-    std::vector<uint32_t> replayer_binary_out;
-    protobufs::TransformationSequence replayer_transformation_sequence_out;
+    // 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());
 
-    Replayer replayer(env, false, validator_options);
-    replayer.SetMessageConsumer(kConsoleMessageConsumer);
-    auto replayer_result_status = replayer.Run(
-        binary_in, initial_facts, fuzzer_transformation_sequence_out,
-        static_cast<uint32_t>(
-            fuzzer_transformation_sequence_out.transformation_size()),
-        &replayer_binary_out, &replayer_transformation_sequence_out);
+    ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result.status);
+    ASSERT_TRUE(t.Validate(fuzzer_result.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)
+            .Run();
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
-              replayer_result_status);
+              replayer_result.status);
 
     // After replaying the transformations applied by the fuzzer, exactly those
     // transformations should have been applied, and the binary resulting from
     // replay should be identical to that which resulted from fuzzing.
     std::string fuzzer_transformations_string;
     std::string replayer_transformations_string;
-    fuzzer_transformation_sequence_out.SerializeToString(
+    fuzzer_result.applied_transformations.SerializeToString(
         &fuzzer_transformations_string);
-    replayer_transformation_sequence_out.SerializeToString(
+    replayer_result.applied_transformations.SerializeToString(
         &replayer_transformations_string);
     ASSERT_EQ(fuzzer_transformations_string, replayer_transformations_string);
-    ASSERT_EQ(fuzzer_binary_out, replayer_binary_out);
+    ASSERT_TRUE(IsEqual(env, fuzzer_result.transformed_binary,
+                        replayer_result.transformed_module.get()));
   }
 }
 
diff --git a/test/fuzz/fuzzer_shrinker_test.cpp b/test/fuzz/fuzzer_shrinker_test.cpp
index 709e9ce..6d9dad3 100644
--- a/test/fuzz/fuzzer_shrinker_test.cpp
+++ b/test/fuzz/fuzzer_shrinker_test.cpp
@@ -12,13 +12,15 @@
 // 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 "source/fuzz/fuzzer.h"
+#include "gtest/gtest.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"
 
@@ -991,25 +993,25 @@
     uint32_t expected_transformations_out_size, uint32_t step_limit,
     spv_validator_options validator_options) {
   // Run the shrinker.
-  Shrinker shrinker(target_env, step_limit, false, validator_options);
-  shrinker.SetMessageConsumer(kSilentConsumer);
+  auto shrinker_result =
+      Shrinker(target_env, kConsoleMessageConsumer, binary_in, initial_facts,
+               transformation_sequence_in, interestingness_function, step_limit,
+               false, validator_options)
+          .Run();
 
-  std::vector<uint32_t> binary_out;
-  protobufs::TransformationSequence transformations_out;
-  Shrinker::ShrinkerResultStatus shrinker_result_status =
-      shrinker.Run(binary_in, initial_facts, transformation_sequence_in,
-                   interestingness_function, &binary_out, &transformations_out);
   ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete ==
-                  shrinker_result_status ||
+                  shrinker_result.status ||
               Shrinker::ShrinkerResultStatus::kStepLimitReached ==
-                  shrinker_result_status);
+                  shrinker_result.status);
 
   // If a non-empty expected binary was provided, check that it matches the
   // result of shrinking and that the expected number of transformations remain.
   if (!expected_binary_out.empty()) {
-    ASSERT_EQ(expected_binary_out, binary_out);
-    ASSERT_EQ(expected_transformations_out_size,
-              static_cast<uint32_t>(transformations_out.transformation_size()));
+    ASSERT_EQ(expected_binary_out, shrinker_result.transformed_binary);
+    ASSERT_EQ(
+        expected_transformations_out_size,
+        static_cast<uint32_t>(
+            shrinker_result.applied_transformations.transformation_size()));
   }
 }
 
@@ -1037,16 +1039,29 @@
   }
 
   // Run the fuzzer and check that it successfully yields a valid binary.
-  std::vector<uint32_t> fuzzer_binary_out;
-  protobufs::TransformationSequence fuzzer_transformation_sequence_out;
   spvtools::ValidatorOptions validator_options;
-  Fuzzer fuzzer(env, seed, true, validator_options);
-  fuzzer.SetMessageConsumer(kSilentConsumer);
-  auto fuzzer_result_status =
-      fuzzer.Run(binary_in, initial_facts, donor_suppliers, &fuzzer_binary_out,
-                 &fuzzer_transformation_sequence_out);
-  ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status);
-  ASSERT_TRUE(t.Validate(fuzzer_binary_out));
+
+  // 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;
+  if ((seed % 3) == 0) {
+    repeated_pass_strategy = Fuzzer::RepeatedPassStrategy::kSimple;
+  } else if ((seed % 3) == 1) {
+    repeated_pass_strategy =
+        Fuzzer::RepeatedPassStrategy::kLoopedWithRecommendations;
+  } else {
+    repeated_pass_strategy =
+        Fuzzer::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));
 
   const uint32_t kReasonableStepLimit = 50;
   const uint32_t kSmallStepLimit = 20;
@@ -1054,30 +1069,30 @@
   // With the AlwaysInteresting test, we should quickly shrink to the original
   // binary with no transformations remaining.
   RunAndCheckShrinker(env, binary_in, initial_facts,
-                      fuzzer_transformation_sequence_out,
+                      fuzzer_result.applied_transformations,
                       AlwaysInteresting().AsFunction(), binary_in, 0,
                       kReasonableStepLimit, validator_options);
 
   // With the OnlyInterestingFirstTime test, no shrinking should be achieved.
   RunAndCheckShrinker(
-      env, binary_in, initial_facts, fuzzer_transformation_sequence_out,
-      OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out,
+      env, binary_in, initial_facts, fuzzer_result.applied_transformations,
+      OnlyInterestingFirstTime().AsFunction(), fuzzer_result.transformed_binary,
       static_cast<uint32_t>(
-          fuzzer_transformation_sequence_out.transformation_size()),
+          fuzzer_result.applied_transformations.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_transformation_sequence_out,
+      env, binary_in, initial_facts, fuzzer_result.applied_transformations,
       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_transformation_sequence_out,
+      env, binary_in, initial_facts, fuzzer_result.applied_transformations,
       InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0,
       kSmallStepLimit, validator_options);
 }
diff --git a/test/fuzz/instruction_descriptor_test.cpp b/test/fuzz/instruction_descriptor_test.cpp
index 5165cfb..3c04d7e 100644
--- a/test/fuzz/instruction_descriptor_test.cpp
+++ b/test/fuzz/instruction_descriptor_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/instruction_descriptor.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -51,7 +54,9 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   for (auto& function : *context->module()) {
     for (auto& block : function) {
diff --git a/test/fuzz/replayer_test.cpp b/test/fuzz/replayer_test.cpp
index 5d6169e..151f263 100644
--- a/test/fuzz/replayer_test.cpp
+++ b/test/fuzz/replayer_test.cpp
@@ -14,7 +14,15 @@
 
 #include "source/fuzz/replayer.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_add_constant_scalar.h"
+#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_parameter.h"
+#include "source/fuzz/transformation_add_synonym.h"
+#include "source/fuzz/transformation_flatten_conditional_branch.h"
 #include "source/fuzz/transformation_split_block.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -75,7 +83,7 @@
 
   std::vector<uint32_t> binary_in;
   SpirvTools t(env);
-  t.SetMessageConsumer(kSilentConsumer);
+  t.SetMessageConsumer(kConsoleMessageConsumer);
   ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption));
   ASSERT_TRUE(t.Validate(binary_in));
 
@@ -89,20 +97,17 @@
 
   {
     // Full replay
-    protobufs::TransformationSequence transformations_out;
     protobufs::FactSequence empty_facts;
-    std::vector<uint32_t> binary_out;
-    Replayer replayer(env, true, validator_options);
-    replayer.SetMessageConsumer(kSilentConsumer);
-    auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 11, &binary_out,
-                     &transformations_out);
+    auto replayer_result =
+        Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts,
+                 transformations, 11, true, validator_options)
+            .Run();
     // Replay should succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
-              replayer_result_status);
+              replayer_result.status);
     // All transformations should be applied.
     ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
-        transformations, transformations_out));
+        transformations, replayer_result.applied_transformations));
 
     const std::string kFullySplitShader = R"(
                OpCapability Shader
@@ -172,28 +177,26 @@
                OpReturn
                OpFunctionEnd
     )";
-    ASSERT_TRUE(IsEqual(env, kFullySplitShader, binary_out));
+    ASSERT_TRUE(IsEqual(env, kFullySplitShader,
+                        replayer_result.transformed_module.get()));
   }
 
   {
     // Half replay
-    protobufs::TransformationSequence transformations_out;
     protobufs::FactSequence empty_facts;
-    std::vector<uint32_t> binary_out;
-    Replayer replayer(env, true, validator_options);
-    replayer.SetMessageConsumer(kSilentConsumer);
-    auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 5, &binary_out,
-                     &transformations_out);
+    auto replayer_result =
+        Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts,
+                 transformations, 5, true, validator_options)
+            .Run();
     // Replay should succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
-              replayer_result_status);
+              replayer_result.status);
     // The first 5 transformations should be applied
-    ASSERT_EQ(5, transformations_out.transformation_size());
+    ASSERT_EQ(5, replayer_result.applied_transformations.transformation_size());
     for (uint32_t i = 0; i < 5; i++) {
       ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
           transformations.transformation(i),
-          transformations_out.transformation(i)));
+          replayer_result.applied_transformations.transformation(i)));
     }
 
     const std::string kHalfSplitShader = R"(
@@ -252,50 +255,221 @@
                OpReturn
                OpFunctionEnd
     )";
-    ASSERT_TRUE(IsEqual(env, kHalfSplitShader, binary_out));
+    ASSERT_TRUE(IsEqual(env, kHalfSplitShader,
+                        replayer_result.transformed_module.get()));
   }
 
   {
     // Empty replay
-    protobufs::TransformationSequence transformations_out;
     protobufs::FactSequence empty_facts;
-    std::vector<uint32_t> binary_out;
-    Replayer replayer(env, true, validator_options);
-    replayer.SetMessageConsumer(kSilentConsumer);
-    auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 0, &binary_out,
-                     &transformations_out);
+    auto replayer_result =
+        Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts,
+                 transformations, 0, true, validator_options)
+            .Run();
     // Replay should succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
-              replayer_result_status);
+              replayer_result.status);
     // No transformations should be applied
-    ASSERT_EQ(0, transformations_out.transformation_size());
-    ASSERT_TRUE(IsEqual(env, kTestShader, binary_out));
+    ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size());
+    ASSERT_TRUE(
+        IsEqual(env, kTestShader, replayer_result.transformed_module.get()));
   }
 
   {
     // Invalid replay: too many transformations
-    protobufs::TransformationSequence transformations_out;
     protobufs::FactSequence empty_facts;
-    std::vector<uint32_t> binary_out;
     // The number of transformations requested to be applied exceeds the number
     // of transformations
-    Replayer replayer(env, true, validator_options);
-    replayer.SetMessageConsumer(kSilentConsumer);
-    auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 12, &binary_out,
-                     &transformations_out);
+    auto replayer_result =
+        Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts,
+                 transformations, 12, true, validator_options)
+            .Run();
 
     // Replay should not succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kTooManyTransformationsRequested,
-              replayer_result_status);
+              replayer_result.status);
     // No transformations should be applied
-    ASSERT_EQ(0, transformations_out.transformation_size());
+    ASSERT_EQ(0, replayer_result.applied_transformations.transformation_size());
     // The output binary should be empty
-    ASSERT_TRUE(binary_out.empty());
+    ASSERT_EQ(nullptr, replayer_result.transformed_module);
   }
 }
 
+TEST(ReplayerTest, CheckFactsAfterReplay) {
+  const std::string kTestShader = 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
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %50 = OpTypePointer Private %8
+         %11 = OpConstant %8 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+               OpStore %10 %11
+         %12 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  spvtools::ValidatorOptions validator_options;
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+  t.SetMessageConsumer(kConsoleMessageConsumer);
+  ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption));
+  ASSERT_TRUE(t.Validate(binary_in));
+
+  protobufs::TransformationSequence transformations;
+  *transformations.add_transformation() =
+      TransformationAddConstantScalar(100, 8, {42}, true).ToMessage();
+  *transformations.add_transformation() =
+      TransformationAddGlobalVariable(101, 50, SpvStorageClassPrivate, 100,
+                                      true)
+          .ToMessage();
+  *transformations.add_transformation() =
+      TransformationAddParameter(6, 102, 8, {{12, 100}}, 103).ToMessage();
+  *transformations.add_transformation() =
+      TransformationAddSynonym(
+          11,
+          protobufs::TransformationAddSynonym::SynonymType::
+              TransformationAddSynonym_SynonymType_COPY_OBJECT,
+          104, MakeInstructionDescriptor(12, SpvOpFunctionCall, 0))
+          .ToMessage();
+
+  // Full replay
+  protobufs::FactSequence empty_facts;
+  auto replayer_result =
+      Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts,
+               transformations, transformations.transformation_size(), true,
+               validator_options)
+          .Run();
+  // Replay should succeed.
+  ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, replayer_result.status);
+  // All transformations should be applied.
+  ASSERT_TRUE(google::protobuf::util::MessageDifferencer::Equals(
+      transformations, replayer_result.applied_transformations));
+
+  const std::string kExpected = 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
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %50 = OpTypePointer Private %8
+         %11 = OpConstant %8 1
+        %100 = OpConstant %8 42
+        %101 = OpVariable %50 Private %100
+        %103 = OpTypeFunction %2 %8
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+               OpStore %10 %11
+        %104 = OpCopyObject %8 %11
+         %12 = OpFunctionCall %2 %6 %100
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %103
+        %102 = OpFunctionParameter %8
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(
+      IsEqual(env, kExpected, replayer_result.transformed_module.get()));
+
+  ASSERT_TRUE(
+      replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant(
+          100));
+  ASSERT_TRUE(replayer_result.transformation_context->GetFactManager()
+                  ->PointeeValueIsIrrelevant(101));
+  ASSERT_TRUE(
+      replayer_result.transformation_context->GetFactManager()->IdIsIrrelevant(
+          102));
+  ASSERT_TRUE(
+      replayer_result.transformation_context->GetFactManager()->IsSynonymous(
+          MakeDataDescriptor(11, {}), MakeDataDescriptor(104, {})));
+}
+
+TEST(ReplayerTest, ReplayWithOverflowIds) {
+  const std::string kTestShader = 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
+         %50 = OpTypePointer Private %6
+          %9 = OpConstant %6 2
+         %11 = OpConstant %6 0
+         %12 = OpTypeBool
+         %17 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+         %10 = OpLoad %6 %8
+         %13 = OpSGreaterThan %12 %10 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %15
+         %14 = OpLabel
+         %16 = OpLoad %6 %8
+         %18 = OpIAdd %6 %16 %17
+               OpStore %8 %18
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  spvtools::ValidatorOptions validator_options;
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(env);
+  t.SetMessageConsumer(kConsoleMessageConsumer);
+  ASSERT_TRUE(t.Assemble(kTestShader, &binary_in, kFuzzAssembleOption));
+  ASSERT_TRUE(t.Validate(binary_in));
+
+  protobufs::TransformationSequence transformations;
+  *transformations.add_transformation() =
+      TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {}).ToMessage();
+  *transformations.add_transformation() =
+      TransformationAddGlobalVariable(101, 50, SpvStorageClassPrivate, 11, true)
+          .ToMessage();
+
+  protobufs::FactSequence empty_facts;
+  auto replayer_result =
+      Replayer(env, kConsoleMessageConsumer, binary_in, empty_facts,
+               transformations, transformations.transformation_size(), true,
+               validator_options)
+          .Run();
+  // Replay should succeed.
+  ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete, replayer_result.status);
+  // All transformations should be applied.
+  ASSERT_EQ(2, replayer_result.applied_transformations.transformation_size());
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/shrinker_test.cpp b/test/fuzz/shrinker_test.cpp
new file mode 100644
index 0000000..42cd182
--- /dev/null
+++ b/test/fuzz/shrinker_test.cpp
@@ -0,0 +1,383 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/shrinker.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fact_manager/fact_manager.h"
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/pseudo_random_generator.h"
+#include "source/fuzz/transformation_context.h"
+#include "source/opt/ir_context.h"
+#include "source/util/make_unique.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(ShrinkerTest, ReduceAddedFunctions) {
+  const std::string kReferenceModule = 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 Private %6
+          %8 = OpVariable %7 Private
+          %9 = OpConstant %6 2
+         %10 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+               OpStore %8 %9
+         %12 = OpLoad %6 %8
+               OpStore %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string kDonorModule = 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 = OpTypeFunction %6 %7
+         %12 = OpTypeFunction %2 %7
+         %17 = OpConstant %6 0
+         %26 = OpTypeBool
+         %32 = OpConstant %6 1
+         %46 = OpTypePointer Private %6
+         %47 = OpVariable %46 Private
+         %48 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %49 = OpVariable %7 Function
+         %50 = OpVariable %7 Function
+         %51 = OpLoad %6 %49
+               OpStore %50 %51
+         %52 = OpFunctionCall %2 %14 %50
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %16 = OpVariable %7 Function
+         %18 = OpVariable %7 Function
+               OpStore %16 %17
+               OpStore %18 %17
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+         %24 = OpLoad %6 %18
+         %25 = OpLoad %6 %9
+         %27 = OpSLessThan %26 %24 %25
+               OpBranchConditional %27 %20 %21
+         %20 = OpLabel
+         %28 = OpLoad %6 %9
+         %29 = OpLoad %6 %16
+         %30 = OpIAdd %6 %29 %28
+               OpStore %16 %30
+               OpBranch %22
+         %22 = OpLabel
+         %31 = OpLoad %6 %18
+         %33 = OpIAdd %6 %31 %32
+               OpStore %18 %33
+               OpBranch %19
+         %21 = OpLabel
+         %34 = OpLoad %6 %16
+         %35 = OpNot %6 %34
+               OpReturnValue %35
+               OpFunctionEnd
+         %14 = OpFunction %2 None %12
+         %13 = OpFunctionParameter %7
+         %15 = OpLabel
+         %37 = OpVariable %7 Function
+         %38 = OpVariable %7 Function
+         %39 = OpLoad %6 %13
+               OpStore %38 %39
+         %40 = OpFunctionCall %6 %10 %38
+               OpStore %37 %40
+         %41 = OpLoad %6 %37
+         %42 = OpLoad %6 %13
+         %43 = OpSGreaterThan %26 %41 %42
+               OpSelectionMerge %45 None
+               OpBranchConditional %43 %44 %45
+         %44 = OpLabel
+               OpStore %47 %48
+               OpBranch %45
+         %45 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Note: |env| should ideally be declared const.  However, due to a known
+  // issue with older versions of MSVC we would have to mark |env| as being
+  // captured due to its used in a lambda below, and other compilers would warn
+  // that such capturing is not necessary.  Not declaring |env| as const means
+  // that it needs to be captured to be used in the lambda, and thus all
+  // compilers are kept happy.  See:
+  // https://developercommunity.visualstudio.com/content/problem/367326/problems-with-capturing-constexpr-in-lambda.html
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = fuzzerutil::kSilentMessageConsumer;
+
+  SpirvTools tools(env);
+  std::vector<uint32_t> reference_binary;
+  ASSERT_TRUE(
+      tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption));
+
+  spvtools::ValidatorOptions validator_options;
+
+  const auto variant_ir_context =
+      BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      variant_ir_context.get(), validator_options, kConsoleMessageConsumer));
+
+  const auto donor_ir_context =
+      BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_ir_context.get(), validator_options, kConsoleMessageConsumer));
+
+  PseudoRandomGenerator random_generator(0);
+  FuzzerContext fuzzer_context(&random_generator, 100);
+  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, {});
+  pass.DonateSingleModule(donor_ir_context.get(), true);
+
+  protobufs::FactSequence no_facts;
+
+  Shrinker::InterestingnessFunction interestingness_function =
+      [consumer, env](const std::vector<uint32_t>& binary,
+                      uint32_t /*unused*/) -> bool {
+    bool found_op_not = false;
+    uint32_t op_call_count = 0;
+    auto temp_ir_context =
+        BuildModule(env, consumer, binary.data(), binary.size());
+    for (auto& function : *temp_ir_context->module()) {
+      for (auto& block : function) {
+        for (auto& inst : block) {
+          if (inst.opcode() == SpvOpNot) {
+            found_op_not = true;
+          } else if (inst.opcode() == SpvOpFunctionCall) {
+            op_call_count++;
+          }
+        }
+      }
+    }
+    return found_op_not && op_call_count >= 2;
+  };
+
+  auto shrinker_result =
+      Shrinker(env, consumer, reference_binary, no_facts, transformations,
+               interestingness_function, 1000, true, validator_options)
+          .Run();
+  ASSERT_EQ(Shrinker::ShrinkerResultStatus::kComplete, shrinker_result.status);
+
+  // We now check that the module after shrinking looks right.
+  // The entry point should be identical to what it looked like in the
+  // reference, while the other functions should be absolutely minimal,
+  // containing only what is needed to satisfy the interestingness function.
+  auto ir_context_after_shrinking =
+      BuildModule(env, consumer, shrinker_result.transformed_binary.data(),
+                  shrinker_result.transformed_binary.size());
+  bool first_function = true;
+  for (auto& function : *ir_context_after_shrinking->module()) {
+    if (first_function) {
+      first_function = false;
+      bool first_block = true;
+      for (auto& block : function) {
+        ASSERT_TRUE(first_block);
+        uint32_t counter = 0;
+        for (auto& inst : block) {
+          switch (counter) {
+            case 0:
+              ASSERT_EQ(SpvOpVariable, inst.opcode());
+              ASSERT_EQ(11, inst.result_id());
+              break;
+            case 1:
+              ASSERT_EQ(SpvOpStore, inst.opcode());
+              break;
+            case 2:
+              ASSERT_EQ(SpvOpLoad, inst.opcode());
+              ASSERT_EQ(12, inst.result_id());
+              break;
+            case 3:
+              ASSERT_EQ(SpvOpStore, inst.opcode());
+              break;
+            case 4:
+              ASSERT_EQ(SpvOpReturn, inst.opcode());
+              break;
+            default:
+              FAIL();
+          }
+          counter++;
+        }
+      }
+    } else {
+      bool first_block = true;
+      for (auto& block : function) {
+        ASSERT_TRUE(first_block);
+        first_block = false;
+        for (auto& inst : block) {
+          switch (inst.opcode()) {
+            case SpvOpVariable:
+            case SpvOpNot:
+            case SpvOpReturn:
+            case SpvOpReturnValue:
+            case SpvOpFunctionCall:
+              // These are the only instructions we expect to see.
+              break;
+            default:
+              FAIL();
+          }
+        }
+      }
+    }
+  }
+}
+
+TEST(ShrinkerTest, HitStepLimitWhenReducingAddedFunctions) {
+  const std::string kReferenceModule = 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 Private %6
+          %8 = OpVariable %7 Private
+          %9 = OpConstant %6 2
+         %10 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpVariable %10 Function
+               OpStore %8 %9
+         %12 = OpLoad %6 %8
+               OpStore %11 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string kDonorModule = 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
+         %48 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %52 = OpCopyObject %6 %48
+         %53 = OpCopyObject %6 %52
+         %54 = OpCopyObject %6 %53
+         %55 = OpCopyObject %6 %54
+         %56 = OpCopyObject %6 %55
+         %57 = OpCopyObject %6 %56
+         %58 = OpCopyObject %6 %48
+         %59 = OpCopyObject %6 %58
+         %60 = OpCopyObject %6 %59
+         %61 = OpCopyObject %6 %60
+         %62 = OpCopyObject %6 %61
+         %63 = OpCopyObject %6 %62
+         %64 = OpCopyObject %6 %48
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  spv_target_env env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = fuzzerutil::kSilentMessageConsumer;
+
+  SpirvTools tools(env);
+  std::vector<uint32_t> reference_binary;
+  ASSERT_TRUE(
+      tools.Assemble(kReferenceModule, &reference_binary, kFuzzAssembleOption));
+
+  spvtools::ValidatorOptions validator_options;
+
+  const auto variant_ir_context =
+      BuildModule(env, consumer, kReferenceModule, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      variant_ir_context.get(), validator_options, kConsoleMessageConsumer));
+
+  const auto donor_ir_context =
+      BuildModule(env, consumer, kDonorModule, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      donor_ir_context.get(), validator_options, kConsoleMessageConsumer));
+
+  PseudoRandomGenerator random_generator(0);
+  FuzzerContext fuzzer_context(&random_generator, 100);
+  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, {});
+  pass.DonateSingleModule(donor_ir_context.get(), true);
+
+  protobufs::FactSequence no_facts;
+
+  Shrinker::InterestingnessFunction interestingness_function =
+      [consumer, env](const std::vector<uint32_t>& binary,
+                      uint32_t /*unused*/) -> bool {
+    auto temp_ir_context =
+        BuildModule(env, consumer, binary.data(), binary.size());
+    uint32_t copy_object_count = 0;
+    temp_ir_context->module()->ForEachInst(
+        [&copy_object_count](opt::Instruction* inst) {
+          if (inst->opcode() == SpvOpCopyObject) {
+            copy_object_count++;
+          }
+
+        });
+    return copy_object_count >= 8;
+  };
+
+  auto shrinker_result =
+      Shrinker(env, consumer, reference_binary, no_facts, transformations,
+               interestingness_function, 30, true, validator_options)
+          .Run();
+  ASSERT_EQ(Shrinker::ShrinkerResultStatus::kStepLimitReached,
+            shrinker_result.status);
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_access_chain_test.cpp b/test/fuzz/transformation_access_chain_test.cpp
index adb14e3..5c43127 100644
--- a/test/fuzz/transformation_access_chain_test.cpp
+++ b/test/fuzz/transformation_access_chain_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_access_chain.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -101,7 +104,9 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Types:
   // Ptr | Pointee | Storage class | GLSL for pointee    | Ids of this type
@@ -117,11 +122,8 @@
 
   // Indices 0-5 are in ids 80-85
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
       54);
 
@@ -217,8 +219,10 @@
         100, 43, {80}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
   }
@@ -228,8 +232,10 @@
         101, 28, {81}, MakeInstructionDescriptor(42, SpvOpReturn, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
   }
@@ -239,8 +245,10 @@
         102, 44, {}, MakeInstructionDescriptor(44, SpvOpStore, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(103));
   }
@@ -250,8 +258,10 @@
         103, 13, {80}, MakeInstructionDescriptor(21, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(104));
   }
@@ -261,8 +271,10 @@
         104, 34, {}, MakeInstructionDescriptor(44, SpvOpStore, 1));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(105));
   }
@@ -272,8 +284,10 @@
         105, 38, {}, MakeInstructionDescriptor(40, SpvOpFunctionCall, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(106));
   }
@@ -283,8 +297,10 @@
         106, 14, {}, MakeInstructionDescriptor(24, SpvOpLoad, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     ASSERT_FALSE(
         transformation_context.GetFactManager()->PointeeValueIsIrrelevant(107));
   }
@@ -401,28 +417,30 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   {
     TransformationAccessChain transformation(
         100, 11, {}, MakeInstructionDescriptor(5, SpvOpReturn, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     TransformationAccessChain transformation(
         101, 12, {}, MakeInstructionDescriptor(5, SpvOpReturn, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
@@ -509,13 +527,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Bad: no ids given for clamping
   ASSERT_FALSE(TransformationAccessChain(
                    100, 29, {17}, MakeInstructionDescriptor(36, SpvOpLoad, 0))
@@ -565,8 +581,10 @@
         {{200, 201}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -575,8 +593,10 @@
         {{202, 203}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -585,8 +605,10 @@
         {{204, 205}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -595,8 +617,10 @@
         {{206, 207}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -605,8 +629,10 @@
         {{208, 209}, {210, 211}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp b/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp
new file mode 100644
index 0000000..9760ed3
--- /dev/null
+++ b/test/fuzz/transformation_add_bit_instruction_synonym_test.cpp
@@ -0,0 +1,910 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_bit_instruction_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(TransformationAddBitInstructionSynonymTest, IsApplicable) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+          %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
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+         %39 = OpBitwiseOr %2 %5 %6
+               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);
+  // Tests undefined bit instruction.
+  auto transformation = TransformationAddBitInstructionSynonym(
+      40, {41,  42,  43,  44,  45,  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});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests false bit instruction.
+  transformation = TransformationAddBitInstructionSynonym(
+      38, {40,  41,  42,  43,  44,  45,  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});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests the number of fresh ids being different than the necessary.
+  transformation = TransformationAddBitInstructionSynonym(
+      39,
+      {40,  41,  42,  43,  44,  45,  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});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests non-fresh ids.
+  transformation = TransformationAddBitInstructionSynonym(
+      39, {38,  40,  41,  42,  43,  44,  45,  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});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable transformation.
+  transformation = TransformationAddBitInstructionSynonym(
+      39, {40,  41,  42,  43,  44,  45,  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});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddBitInstructionSynonymTest, AddOpBitwiseOrSynonym) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+          %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
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+         %39 = OpBitwiseOr %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);
+
+  // Adds OpBitwiseOr synonym.
+  auto transformation = TransformationAddBitInstructionSynonym(
+      39, {40,  41,  42,  43,  44,  45,  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});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(166, {}), MakeDataDescriptor(39, {})));
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+          %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
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+
+; Add OpBitwiseOr synonym
+         %40 = OpBitFieldUExtract %2 %5 %5 %6 ; extracts bit 0 from %5
+         %41 = OpBitFieldUExtract %2 %6 %5 %6 ; extracts bit 0 from %6
+         %42 = OpBitwiseOr %2 %40 %41
+
+         %43 = OpBitFieldUExtract %2 %5 %6 %6 ; extracts bit 1 from %5
+         %44 = OpBitFieldUExtract %2 %6 %6 %6 ; extracts bit 1 from %6
+         %45 = OpBitwiseOr %2 %43 %44
+
+         %46 = OpBitFieldUExtract %2 %5 %7 %6 ; extracts bit 2 from %5
+         %47 = OpBitFieldUExtract %2 %6 %7 %6 ; extracts bit 2 from %6
+         %48 = OpBitwiseOr %2 %46 %47
+
+         %49 = OpBitFieldUExtract %2 %5 %8 %6 ; extracts bit 3 from %5
+         %50 = OpBitFieldUExtract %2 %6 %8 %6 ; extracts bit 3 from %6
+         %51 = OpBitwiseOr %2 %49 %50
+
+         %52 = OpBitFieldUExtract %2 %5 %9 %6 ; extracts bit 4 from %5
+         %53 = OpBitFieldUExtract %2 %6 %9 %6 ; extracts bit 4 from %6
+         %54 = OpBitwiseOr %2 %52 %53
+
+         %55 = OpBitFieldUExtract %2 %5 %10 %6 ; extracts bit 5 from %5
+         %56 = OpBitFieldUExtract %2 %6 %10 %6 ; extracts bit 5 from %6
+         %57 = OpBitwiseOr %2 %55 %56
+
+         %58 = OpBitFieldUExtract %2 %5 %11 %6 ; extracts bit 6 from %5
+         %59 = OpBitFieldUExtract %2 %6 %11 %6 ; extracts bit 6 from %6
+         %60 = OpBitwiseOr %2 %58 %59
+
+         %61 = OpBitFieldUExtract %2 %5 %12 %6 ; extracts bit 7 from %5
+         %62 = OpBitFieldUExtract %2 %6 %12 %6 ; extracts bit 7 from %6
+         %63 = OpBitwiseOr %2 %61 %62
+
+         %64 = OpBitFieldUExtract %2 %5 %13 %6 ; extracts bit 8 from %5
+         %65 = OpBitFieldUExtract %2 %6 %13 %6 ; extracts bit 8 from %6
+         %66 = OpBitwiseOr %2 %64 %65
+
+         %67 = OpBitFieldUExtract %2 %5 %14 %6 ; extracts bit 9 from %5
+         %68 = OpBitFieldUExtract %2 %6 %14 %6 ; extracts bit 9 from %6
+         %69 = OpBitwiseOr %2 %67 %68
+
+         %70 = OpBitFieldUExtract %2 %5 %15 %6 ; extracts bit 10 from %5
+         %71 = OpBitFieldUExtract %2 %6 %15 %6 ; extracts bit 10 from %6
+         %72 = OpBitwiseOr %2 %70 %71
+
+         %73 = OpBitFieldUExtract %2 %5 %16 %6 ; extracts bit 11 from %5
+         %74 = OpBitFieldUExtract %2 %6 %16 %6 ; extracts bit 11 from %6
+         %75 = OpBitwiseOr %2 %73 %74
+
+         %76 = OpBitFieldUExtract %2 %5 %17 %6 ; extracts bit 12 from %5
+         %77 = OpBitFieldUExtract %2 %6 %17 %6 ; extracts bit 12 from %6
+         %78 = OpBitwiseOr %2 %76 %77
+
+         %79 = OpBitFieldUExtract %2 %5 %18 %6 ; extracts bit 13 from %5
+         %80 = OpBitFieldUExtract %2 %6 %18 %6 ; extracts bit 13 from %6
+         %81 = OpBitwiseOr %2 %79 %80
+
+         %82 = OpBitFieldUExtract %2 %5 %19 %6 ; extracts bit 14 from %5
+         %83 = OpBitFieldUExtract %2 %6 %19 %6 ; extracts bit 14 from %6
+         %84 = OpBitwiseOr %2 %82 %83
+
+         %85 = OpBitFieldUExtract %2 %5 %20 %6 ; extracts bit 15 from %5
+         %86 = OpBitFieldUExtract %2 %6 %20 %6 ; extracts bit 15 from %6
+         %87 = OpBitwiseOr %2 %85 %86
+
+         %88 = OpBitFieldUExtract %2 %5 %21 %6 ; extracts bit 16 from %5
+         %89 = OpBitFieldUExtract %2 %6 %21 %6 ; extracts bit 16 from %6
+         %90 = OpBitwiseOr %2 %88 %89
+
+         %91 = OpBitFieldUExtract %2 %5 %22 %6 ; extracts bit 17 from %5
+         %92 = OpBitFieldUExtract %2 %6 %22 %6 ; extracts bit 17 from %6
+         %93 = OpBitwiseOr %2 %91 %92
+
+         %94 = OpBitFieldUExtract %2 %5 %23 %6 ; extracts bit 18 from %5
+         %95 = OpBitFieldUExtract %2 %6 %23 %6 ; extracts bit 18 from %6
+         %96 = OpBitwiseOr %2 %94 %95
+
+         %97 = OpBitFieldUExtract %2 %5 %24 %6 ; extracts bit 19 from %5
+         %98 = OpBitFieldUExtract %2 %6 %24 %6 ; extracts bit 19 from %6
+         %99 = OpBitwiseOr %2 %97 %98
+
+        %100 = OpBitFieldUExtract %2 %5 %25 %6 ; extracts bit 20 from %5
+        %101 = OpBitFieldUExtract %2 %6 %25 %6 ; extracts bit 20 from %6
+        %102 = OpBitwiseOr %2 %100 %101
+
+        %103 = OpBitFieldUExtract %2 %5 %26 %6 ; extracts bit 21 from %5
+        %104 = OpBitFieldUExtract %2 %6 %26 %6 ; extracts bit 21 from %6
+        %105 = OpBitwiseOr %2 %103 %104
+
+        %106 = OpBitFieldUExtract %2 %5 %27 %6 ; extracts bit 22 from %5
+        %107 = OpBitFieldUExtract %2 %6 %27 %6 ; extracts bit 22 from %6
+        %108 = OpBitwiseOr %2 %106 %107
+
+        %109 = OpBitFieldUExtract %2 %5 %28 %6 ; extracts bit 23 from %5
+        %110 = OpBitFieldUExtract %2 %6 %28 %6 ; extracts bit 23 from %6
+        %111 = OpBitwiseOr %2 %109 %110
+
+        %112 = OpBitFieldUExtract %2 %5 %29 %6 ; extracts bit 24 from %5
+        %113 = OpBitFieldUExtract %2 %6 %29 %6 ; extracts bit 24 from %6
+        %114 = OpBitwiseOr %2 %112 %113
+
+        %115 = OpBitFieldUExtract %2 %5 %30 %6 ; extracts bit 25 from %5
+        %116 = OpBitFieldUExtract %2 %6 %30 %6 ; extracts bit 25 from %6
+        %117 = OpBitwiseOr %2 %115 %116
+
+        %118 = OpBitFieldUExtract %2 %5 %31 %6 ; extracts bit 26 from %5
+        %119 = OpBitFieldUExtract %2 %6 %31 %6 ; extracts bit 26 from %6
+        %120 = OpBitwiseOr %2 %118 %119
+
+        %121 = OpBitFieldUExtract %2 %5 %32 %6 ; extracts bit 27 from %5
+        %122 = OpBitFieldUExtract %2 %6 %32 %6 ; extracts bit 27 from %6
+        %123 = OpBitwiseOr %2 %121 %122
+
+        %124 = OpBitFieldUExtract %2 %5 %33 %6 ; extracts bit 28 from %5
+        %125 = OpBitFieldUExtract %2 %6 %33 %6 ; extracts bit 28 from %6
+        %126 = OpBitwiseOr %2 %124 %125
+
+        %127 = OpBitFieldUExtract %2 %5 %34 %6 ; extracts bit 29 from %5
+        %128 = OpBitFieldUExtract %2 %6 %34 %6 ; extracts bit 29 from %6
+        %129 = OpBitwiseOr %2 %127 %128
+
+        %130 = OpBitFieldUExtract %2 %5 %35 %6 ; extracts bit 30 from %5
+        %131 = OpBitFieldUExtract %2 %6 %35 %6 ; extracts bit 30 from %6
+        %132 = OpBitwiseOr %2 %130 %131
+
+        %133 = OpBitFieldUExtract %2 %5 %36 %6 ; extracts bit 31 from %5
+        %134 = OpBitFieldUExtract %2 %6 %36 %6 ; extracts bit 31 from %6
+        %135 = OpBitwiseOr %2 %133 %134
+
+        %136 = OpBitFieldInsert %2 %42 %45 %6 %6 ; inserts bit 1
+        %137 = OpBitFieldInsert %2 %136 %48 %7 %6 ; inserts bit 2
+        %138 = OpBitFieldInsert %2 %137 %51 %8 %6 ; inserts bit 3
+        %139 = OpBitFieldInsert %2 %138 %54 %9 %6 ; inserts bit 4
+        %140 = OpBitFieldInsert %2 %139 %57 %10 %6 ; inserts bit 5
+        %141 = OpBitFieldInsert %2 %140 %60 %11 %6 ; inserts bit 6
+        %142 = OpBitFieldInsert %2 %141 %63 %12 %6 ; inserts bit 7
+        %143 = OpBitFieldInsert %2 %142 %66 %13 %6 ; inserts bit 8
+        %144 = OpBitFieldInsert %2 %143 %69 %14 %6 ; inserts bit 9
+        %145 = OpBitFieldInsert %2 %144 %72 %15 %6 ; inserts bit 10
+        %146 = OpBitFieldInsert %2 %145 %75 %16 %6 ; inserts bit 11
+        %147 = OpBitFieldInsert %2 %146 %78 %17 %6 ; inserts bit 12
+        %148 = OpBitFieldInsert %2 %147 %81 %18 %6 ; inserts bit 13
+        %149 = OpBitFieldInsert %2 %148 %84 %19 %6 ; inserts bit 14
+        %150 = OpBitFieldInsert %2 %149 %87 %20 %6 ; inserts bit 15
+        %151 = OpBitFieldInsert %2 %150 %90 %21 %6 ; inserts bit 16
+        %152 = OpBitFieldInsert %2 %151 %93 %22 %6 ; inserts bit 17
+        %153 = OpBitFieldInsert %2 %152 %96 %23 %6 ; inserts bit 18
+        %154 = OpBitFieldInsert %2 %153 %99 %24 %6 ; inserts bit 19
+        %155 = OpBitFieldInsert %2 %154 %102 %25 %6 ; inserts bit 20
+        %156 = OpBitFieldInsert %2 %155 %105 %26 %6 ; inserts bit 21
+        %157 = OpBitFieldInsert %2 %156 %108 %27 %6 ; inserts bit 22
+        %158 = OpBitFieldInsert %2 %157 %111 %28 %6 ; inserts bit 23
+        %159 = OpBitFieldInsert %2 %158 %114 %29 %6 ; inserts bit 24
+        %160 = OpBitFieldInsert %2 %159 %117 %30 %6 ; inserts bit 25
+        %161 = OpBitFieldInsert %2 %160 %120 %31 %6 ; inserts bit 26
+        %162 = OpBitFieldInsert %2 %161 %123 %32 %6 ; inserts bit 27
+        %163 = OpBitFieldInsert %2 %162 %126 %33 %6 ; inserts bit 28
+        %164 = OpBitFieldInsert %2 %163 %129 %34 %6 ; inserts bit 29
+        %165 = OpBitFieldInsert %2 %164 %132 %35 %6 ; inserts bit 30
+        %166 = OpBitFieldInsert %2 %165 %135 %36 %6 ; inserts bit 31
+         %39 = OpBitwiseOr %2 %5 %6
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+TEST(TransformationAddBitInstructionSynonymTest, AddOpNotSynonym) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+          %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
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+         %39 = OpNot %2 %5 ; 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);
+
+  // Adds OpNot synonym.
+  auto transformation = TransformationAddBitInstructionSynonym(
+      39, {40,  41,  42,  43,  44,  45,  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});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(134, {}), MakeDataDescriptor(39, {})));
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+          %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
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+
+; Add OpNot synonym
+         %40 = OpBitFieldUExtract %2 %5 %5 %6 ; extracts bit 0 from %5
+         %41 = OpNot %2 %40
+
+         %42 = OpBitFieldUExtract %2 %5 %6 %6 ; extracts bit 1 from %5
+         %43 = OpNot %2 %42
+
+         %44 = OpBitFieldUExtract %2 %5 %7 %6 ; extracts bit 2 from %5
+         %45 = OpNot %2 %44
+
+         %46 = OpBitFieldUExtract %2 %5 %8 %6 ; extracts bit 3 from %5
+         %47 = OpNot %2 %46
+
+         %48 = OpBitFieldUExtract %2 %5 %9 %6 ; extracts bit 4 from %5
+         %49 = OpNot %2 %48
+
+         %50 = OpBitFieldUExtract %2 %5 %10 %6 ; extracts bit 5 from %5
+         %51 = OpNot %2 %50
+
+         %52 = OpBitFieldUExtract %2 %5 %11 %6 ; extracts bit 6 from %5
+         %53 = OpNot %2 %52
+
+         %54 = OpBitFieldUExtract %2 %5 %12 %6 ; extracts bit 7 from %5
+         %55 = OpNot %2 %54
+
+         %56 = OpBitFieldUExtract %2 %5 %13 %6 ; extracts bit 8 from %5
+         %57 = OpNot %2 %56
+
+         %58 = OpBitFieldUExtract %2 %5 %14 %6 ; extracts bit 9 from %5
+         %59 = OpNot %2 %58
+
+         %60 = OpBitFieldUExtract %2 %5 %15 %6 ; extracts bit 10 from %5
+         %61 = OpNot %2 %60
+
+         %62 = OpBitFieldUExtract %2 %5 %16 %6 ; extracts bit 11 from %5
+         %63 = OpNot %2 %62
+
+         %64 = OpBitFieldUExtract %2 %5 %17 %6 ; extracts bit 12 from %5
+         %65 = OpNot %2 %64
+
+         %66 = OpBitFieldUExtract %2 %5 %18 %6 ; extracts bit 13 from %5
+         %67 = OpNot %2 %66
+
+         %68 = OpBitFieldUExtract %2 %5 %19 %6 ; extracts bit 14 from %5
+         %69 = OpNot %2 %68
+
+         %70 = OpBitFieldUExtract %2 %5 %20 %6 ; extracts bit 15 from %5
+         %71 = OpNot %2 %70
+
+         %72 = OpBitFieldUExtract %2 %5 %21 %6 ; extracts bit 16 from %5
+         %73 = OpNot %2 %72
+
+         %74 = OpBitFieldUExtract %2 %5 %22 %6 ; extracts bit 17 from %5
+         %75 = OpNot %2 %74
+
+         %76 = OpBitFieldUExtract %2 %5 %23 %6 ; extracts bit 18 from %5
+         %77 = OpNot %2 %76
+
+         %78 = OpBitFieldUExtract %2 %5 %24 %6 ; extracts bit 19 from %5
+         %79 = OpNot %2 %78
+
+         %80 = OpBitFieldUExtract %2 %5 %25 %6 ; extracts bit 20 from %5
+         %81 = OpNot %2 %80
+
+         %82 = OpBitFieldUExtract %2 %5 %26 %6 ; extracts bit 21 from %5
+         %83 = OpNot %2 %82
+
+         %84 = OpBitFieldUExtract %2 %5 %27 %6 ; extracts bit 22 from %5
+         %85 = OpNot %2 %84
+
+         %86 = OpBitFieldUExtract %2 %5 %28 %6 ; extracts bit 23 from %5
+         %87 = OpNot %2 %86
+
+         %88 = OpBitFieldUExtract %2 %5 %29 %6 ; extracts bit 24 from %5
+         %89 = OpNot %2 %88
+
+         %90 = OpBitFieldUExtract %2 %5 %30 %6 ; extracts bit 25 from %5
+         %91 = OpNot %2 %90
+
+         %92 = OpBitFieldUExtract %2 %5 %31 %6 ; extracts bit 26 from %5
+         %93 = OpNot %2 %92
+
+         %94 = OpBitFieldUExtract %2 %5 %32 %6 ; extracts bit 27 from %5
+         %95 = OpNot %2 %94
+
+         %96 = OpBitFieldUExtract %2 %5 %33 %6 ; extracts bit 28 from %5
+         %97 = OpNot %2 %96
+
+         %98 = OpBitFieldUExtract %2 %5 %34 %6 ; extracts bit 29 from %5
+         %99 = OpNot %2 %98
+
+        %100 = OpBitFieldUExtract %2 %5 %35 %6 ; extracts bit 30 from %5
+        %101 = OpNot %2 %100
+
+        %102 = OpBitFieldUExtract %2 %5 %36 %6 ; extracts bit 31 from %5
+        %103 = OpNot %2 %102
+
+        %104 = OpBitFieldInsert %2 %41 %43 %6 %6 ; inserts bit 1
+        %105 = OpBitFieldInsert %2 %104 %45 %7 %6 ; inserts bit 2
+        %106 = OpBitFieldInsert %2 %105 %47 %8 %6 ; inserts bit 3
+        %107 = OpBitFieldInsert %2 %106 %49 %9 %6 ; inserts bit 4
+        %108 = OpBitFieldInsert %2 %107 %51 %10 %6 ; inserts bit 5
+        %109 = OpBitFieldInsert %2 %108 %53 %11 %6 ; inserts bit 6
+        %110 = OpBitFieldInsert %2 %109 %55 %12 %6 ; inserts bit 7
+        %111 = OpBitFieldInsert %2 %110 %57 %13 %6 ; inserts bit 8
+        %112 = OpBitFieldInsert %2 %111 %59 %14 %6 ; inserts bit 9
+        %113 = OpBitFieldInsert %2 %112 %61 %15 %6 ; inserts bit 10
+        %114 = OpBitFieldInsert %2 %113 %63 %16 %6 ; inserts bit 11
+        %115 = OpBitFieldInsert %2 %114 %65 %17 %6 ; inserts bit 12
+        %116 = OpBitFieldInsert %2 %115 %67 %18 %6 ; inserts bit 13
+        %117 = OpBitFieldInsert %2 %116 %69 %19 %6 ; inserts bit 14
+        %118 = OpBitFieldInsert %2 %117 %71 %20 %6 ; inserts bit 15
+        %119 = OpBitFieldInsert %2 %118 %73 %21 %6 ; inserts bit 16
+        %120 = OpBitFieldInsert %2 %119 %75 %22 %6 ; inserts bit 17
+        %121 = OpBitFieldInsert %2 %120 %77 %23 %6 ; inserts bit 18
+        %122 = OpBitFieldInsert %2 %121 %79 %24 %6 ; inserts bit 19
+        %123 = OpBitFieldInsert %2 %122 %81 %25 %6 ; inserts bit 20
+        %124 = OpBitFieldInsert %2 %123 %83 %26 %6 ; inserts bit 21
+        %125 = OpBitFieldInsert %2 %124 %85 %27 %6 ; inserts bit 22
+        %126 = OpBitFieldInsert %2 %125 %87 %28 %6 ; inserts bit 23
+        %127 = OpBitFieldInsert %2 %126 %89 %29 %6 ; inserts bit 24
+        %128 = OpBitFieldInsert %2 %127 %91 %30 %6 ; inserts bit 25
+        %129 = OpBitFieldInsert %2 %128 %93 %31 %6 ; inserts bit 26
+        %130 = OpBitFieldInsert %2 %129 %95 %32 %6 ; inserts bit 27
+        %131 = OpBitFieldInsert %2 %130 %97 %33 %6 ; inserts bit 28
+        %132 = OpBitFieldInsert %2 %131 %99 %34 %6 ; inserts bit 29
+        %133 = OpBitFieldInsert %2 %132 %101 %35 %6 ; inserts bit 30
+        %134 = OpBitFieldInsert %2 %133 %103 %36 %6 ; inserts bit 31
+         %39 = OpNot %2 %5
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+TEST(TransformationAddBitInstructionSynonymTest, NoSynonymWhenIdIsIrrelevant) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+          %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
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+         %39 = OpBitwiseOr %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);
+
+  // Mark the result id of the bit instruction as irrelevant.
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(39);
+
+  // Adds OpBitwiseOr synonym.
+  auto transformation = TransformationAddBitInstructionSynonym(
+      39, {40,  41,  42,  43,  44,  45,  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});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  // No synonym should have been created, since the bit instruction is
+  // irrelevant.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(166, {}), MakeDataDescriptor(39, {})));
+}
+
+TEST(TransformationAddBitInstructionSynonymTest, NoSynonymWhenBlockIsDead) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %37 "main"
+
+; Types
+          %2 = OpTypeInt 32 0
+          %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
+
+; main function
+         %37 = OpFunction %3 None %4
+         %38 = OpLabel
+         %39 = OpBitwiseOr %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);
+
+  // Mark the block where we will try to create the synonym as dead.
+  transformation_context.GetFactManager()->AddFactBlockIsDead(38);
+
+  // Adds OpBitwiseOr synonym.
+  auto transformation = TransformationAddBitInstructionSynonym(
+      39, {40,  41,  42,  43,  44,  45,  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});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  // No synonym should have been created, since the bit instruction is
+  // irrelevant.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(166, {}), MakeDataDescriptor(39, {})));
+}
+
+}  // 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 1a40329..3506db6 100644
--- a/test/fuzz/transformation_add_constant_boolean_test.cpp
+++ b/test/fuzz/transformation_add_constant_boolean_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_add_constant_boolean.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -41,13 +43,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               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.
   ASSERT_TRUE(TransformationAddConstantBoolean(7, true, false)
                   .IsApplicable(context.get(), transformation_context));
@@ -68,8 +68,9 @@
   auto add_false = TransformationAddConstantBoolean(8, false, false);
 
   ASSERT_TRUE(add_true.IsApplicable(context.get(), transformation_context));
-  add_true.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_true, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Having added true, we cannot add it again with the same id.
   ASSERT_FALSE(add_true.IsApplicable(context.get(), transformation_context));
@@ -77,12 +78,14 @@
   auto add_true_again = TransformationAddConstantBoolean(100, true, false);
   ASSERT_TRUE(
       add_true_again.IsApplicable(context.get(), transformation_context));
-  add_true_again.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_true_again, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(add_false.IsApplicable(context.get(), transformation_context));
-  add_false.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_false, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Having added false, we cannot add it again with the same id.
   ASSERT_FALSE(add_false.IsApplicable(context.get(), transformation_context));
@@ -90,27 +93,33 @@
   auto add_false_again = TransformationAddConstantBoolean(101, false, false);
   ASSERT_TRUE(
       add_false_again.IsApplicable(context.get(), transformation_context));
-  add_false_again.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_false_again, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // We can create an irrelevant OpConstantTrue.
   TransformationAddConstantBoolean irrelevant_true(102, true, true);
   ASSERT_TRUE(
       irrelevant_true.IsApplicable(context.get(), transformation_context));
-  irrelevant_true.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(irrelevant_true, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // We can create an irrelevant OpConstantFalse.
   TransformationAddConstantBoolean irrelevant_false(103, false, true);
   ASSERT_TRUE(
       irrelevant_false.IsApplicable(context.get(), transformation_context));
-  irrelevant_false.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(irrelevant_false, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
-  ASSERT_FALSE(fact_manager.IdIsIrrelevant(100));
-  ASSERT_FALSE(fact_manager.IdIsIrrelevant(101));
-  ASSERT_TRUE(fact_manager.IdIsIrrelevant(102));
-  ASSERT_TRUE(fact_manager.IdIsIrrelevant(103));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(100));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(101));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(102));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(103));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -158,13 +167,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Neither true nor false can be added as OpTypeBool is not present.
   ASSERT_FALSE(TransformationAddConstantBoolean(6, true, false)
                    .IsApplicable(context.get(), transformation_context));
diff --git a/test/fuzz/transformation_add_constant_composite_test.cpp b/test/fuzz/transformation_add_constant_composite_test.cpp
index 75e23ad..2c296fb 100644
--- a/test/fuzz/transformation_add_constant_composite_test.cpp
+++ b/test/fuzz/transformation_add_constant_composite_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_add_constant_composite.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -62,13 +64,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Too few ids
   ASSERT_FALSE(TransformationAddConstantComposite(103, 8, {100, 101}, false)
                    .IsApplicable(context.get(), transformation_context));
@@ -130,16 +130,18 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   for (uint32_t id = 100; id <= 106; ++id) {
-    ASSERT_FALSE(fact_manager.IdIsIrrelevant(id));
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(id));
   }
 
   for (uint32_t id = 107; id <= 113; ++id) {
-    ASSERT_TRUE(fact_manager.IdIsIrrelevant(id));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(id));
   }
 
   std::string after_transformation = R"(
@@ -196,6 +198,92 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationAddConstantCompositeTest, DisallowBufferBlockDecoration) {
+  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
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 4
+               OpDecorate %7 BufferBlock
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %10 = OpConstant %6 42
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_0;
+  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);
+  ASSERT_FALSE(TransformationAddConstantComposite(100, 7, {10, 10}, false)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddConstantCompositeTest, DisallowBlockDecoration) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %9
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 4
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %10 = OpConstant %6 42
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer StorageBuffer %7
+          %9 = OpVariable %8 StorageBuffer
+          %4 = OpFunction %2 None %3
+          %5 = 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);
+  ASSERT_FALSE(TransformationAddConstantComposite(100, 7, {10, 10}, false)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_add_constant_null_test.cpp b/test/fuzz/transformation_add_constant_null_test.cpp
index 0bfee34..ce20a67 100644
--- a/test/fuzz/transformation_add_constant_null_test.cpp
+++ b/test/fuzz/transformation_add_constant_null_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_constant_null.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -47,13 +50,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(TransformationAddConstantNull(4, 11).IsApplicable(
       context.get(), transformation_context));
@@ -99,9 +100,11 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_constant_scalar_test.cpp b/test/fuzz/transformation_add_constant_scalar_test.cpp
index 7d9608d..a153fb1 100644
--- a/test/fuzz/transformation_add_constant_scalar_test.cpp
+++ b/test/fuzz/transformation_add_constant_scalar_test.cpp
@@ -14,246 +14,341 @@
 
 #include "source/fuzz/transformation_add_constant_scalar.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
 namespace fuzz {
 namespace {
 
-TEST(TransformationAddConstantScalarTest, BasicTest) {
-  std::string shader = R"(
+TEST(TransformationAddConstantScalarTest, IsApplicable) {
+  std::string reference_shader = R"(
                OpCapability Shader
+               OpCapability Int64
+               OpCapability Float64
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "x"
-               OpName %12 "y"
-               OpName %16 "z"
-               OpDecorate %8 RelaxedPrecision
-               OpDecorate %12 RelaxedPrecision
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 1
-         %10 = OpTypeInt 32 0
-         %11 = OpTypePointer Function %10
-         %13 = OpConstant %10 2
-         %14 = OpTypeFloat 32
-         %15 = OpTypePointer Function %14
-         %17 = OpConstant %14 3
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %12 = OpVariable %11 Function
-         %16 = OpVariable %15 Function
-               OpStore %8 %9
-               OpStore %12 %13
-               OpStore %16 %17
+               OpEntryPoint Vertex %17 "main"
+
+; Types
+
+  ; 32-bit types
+          %2 = OpTypeInt 32 0
+          %3 = OpTypeInt 32 1
+          %4 = OpTypeFloat 32
+
+  ; 64-bit types
+          %5 = OpTypeInt 64 0
+          %6 = OpTypeInt 64 1
+          %7 = OpTypeFloat 64
+
+          %8 = OpTypePointer Private %2
+          %9 = OpTypeVoid
+         %10 = OpTypeFunction %9
+
+; Constants
+
+  ; 32-bit constants
+         %11 = OpConstant %2 1
+         %12 = OpConstant %3 2
+         %13 = OpConstant %4 3
+
+  ; 64-bit constants
+         %14 = OpConstant %5 1
+         %15 = OpConstant %6 2
+         %16 = OpConstant %7 3
+
+; main function
+         %17 = OpFunction %9 None %10
+         %18 = OpLabel
                OpReturn
                OpFunctionEnd
   )";
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  const float float_values[2] = {3.0, 30.0};
-  uint32_t uint_for_float[2];
-  memcpy(uint_for_float, float_values, sizeof(float_values));
-
-  auto add_signed_int_1 = TransformationAddConstantScalar(100, 6, {1}, false);
-  auto add_signed_int_10 = TransformationAddConstantScalar(101, 6, {10}, false);
-  auto add_unsigned_int_2 =
-      TransformationAddConstantScalar(102, 10, {2}, false);
-  auto add_unsigned_int_20 =
-      TransformationAddConstantScalar(103, 10, {20}, false);
-  auto add_float_3 =
-      TransformationAddConstantScalar(104, 14, {uint_for_float[0]}, false);
-  auto add_float_30 =
-      TransformationAddConstantScalar(105, 14, {uint_for_float[1]}, false);
-  auto add_signed_int_1_irrelevant =
-      TransformationAddConstantScalar(106, 6, {1}, true);
-  auto add_signed_int_10_irrelevant =
-      TransformationAddConstantScalar(107, 6, {10}, true);
-  auto add_unsigned_int_2_irrelevant =
-      TransformationAddConstantScalar(108, 10, {2}, true);
-  auto add_unsigned_int_20_irrelevant =
-      TransformationAddConstantScalar(109, 10, {20}, true);
-  auto add_float_3_irrelevant =
-      TransformationAddConstantScalar(110, 14, {uint_for_float[0]}, true);
-  auto add_float_30_irrelevant =
-      TransformationAddConstantScalar(111, 14, {uint_for_float[1]}, true);
-  auto bad_add_float_30_id_already_used =
-      TransformationAddConstantScalar(104, 14, {uint_for_float[1]}, false);
-  auto bad_id_already_used = TransformationAddConstantScalar(1, 6, {1}, false);
-  auto bad_no_data = TransformationAddConstantScalar(100, 6, {}, false);
-  auto bad_too_much_data =
-      TransformationAddConstantScalar(100, 6, {1, 2}, false);
-  auto bad_type_id_does_not_exist =
-      TransformationAddConstantScalar(108, 2020, {uint_for_float[0]}, false);
-  auto bad_type_id_is_not_a_type =
-      TransformationAddConstantScalar(109, 9, {0}, false);
-  auto bad_type_id_is_void =
-      TransformationAddConstantScalar(110, 2, {0}, false);
-  auto bad_type_id_is_pointer =
-      TransformationAddConstantScalar(111, 11, {0}, false);
-
-  // Id is already in use.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Tests |fresh_id| being non-fresh.
+  auto transformation = TransformationAddConstantScalar(18, 2, {0}, false);
   ASSERT_FALSE(
-      bad_id_already_used.IsApplicable(context.get(), transformation_context));
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // At least one word of data must be provided.
-  ASSERT_FALSE(bad_no_data.IsApplicable(context.get(), transformation_context));
-
-  // Cannot give two data words for a 32-bit type.
+  // Tests undefined |type_id|.
+  transformation = TransformationAddConstantScalar(19, 20, {0}, false);
   ASSERT_FALSE(
-      bad_too_much_data.IsApplicable(context.get(), transformation_context));
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // Type id does not exist
-  ASSERT_FALSE(bad_type_id_does_not_exist.IsApplicable(context.get(),
-                                                       transformation_context));
-
-  // Type id is not a type
-  ASSERT_FALSE(bad_type_id_is_not_a_type.IsApplicable(context.get(),
-                                                      transformation_context));
-
-  // Type id is void
+  // Tests |type_id| not representing a type instruction.
+  transformation = TransformationAddConstantScalar(19, 11, {0}, false);
   ASSERT_FALSE(
-      bad_type_id_is_void.IsApplicable(context.get(), transformation_context));
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // Type id is pointer
-  ASSERT_FALSE(bad_type_id_is_pointer.IsApplicable(context.get(),
-                                                   transformation_context));
+  // Tests |type_id| representing an OpTypePointer instruction.
+  transformation = TransformationAddConstantScalar(19, 8, {0}, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(
-      add_signed_int_1.IsApplicable(context.get(), transformation_context));
-  add_signed_int_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  // Tests |type_id| representing an OpTypeVoid instruction.
+  transformation = TransformationAddConstantScalar(19, 9, {0}, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(
-      add_signed_int_10.IsApplicable(context.get(), transformation_context));
-  add_signed_int_10.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  // Tests |words| having no words.
+  transformation = TransformationAddConstantScalar(19, 2, {}, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(
-      add_unsigned_int_2.IsApplicable(context.get(), transformation_context));
-  add_unsigned_int_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  // Tests |words| having 2 words for a 32-bit type.
+  transformation = TransformationAddConstantScalar(19, 2, {0, 1}, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(
-      add_unsigned_int_20.IsApplicable(context.get(), transformation_context));
-  add_unsigned_int_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  // Tests |words| having 3 words for a 64-bit type.
+  transformation = TransformationAddConstantScalar(19, 5, {0, 1, 2}, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
 
-  ASSERT_TRUE(add_float_3.IsApplicable(context.get(), transformation_context));
-  add_float_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_TRUE(add_float_30.IsApplicable(context.get(), transformation_context));
-  add_float_30.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  // Add irrelevant ids.
-  ASSERT_TRUE(add_signed_int_1_irrelevant.IsApplicable(context.get(),
-                                                       transformation_context));
-  add_signed_int_1_irrelevant.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_TRUE(add_signed_int_10_irrelevant.IsApplicable(
-      context.get(), transformation_context));
-  add_signed_int_10_irrelevant.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_TRUE(add_unsigned_int_2_irrelevant.IsApplicable(
-      context.get(), transformation_context));
-  add_unsigned_int_2_irrelevant.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_TRUE(add_unsigned_int_20_irrelevant.IsApplicable(
-      context.get(), transformation_context));
-  add_unsigned_int_20_irrelevant.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_TRUE(add_float_3_irrelevant.IsApplicable(context.get(),
-                                                  transformation_context));
-  add_float_3_irrelevant.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_TRUE(add_float_30_irrelevant.IsApplicable(context.get(),
-                                                   transformation_context));
-  add_float_30_irrelevant.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_FALSE(bad_add_float_30_id_already_used.IsApplicable(
-      context.get(), transformation_context));
-
-  for (uint32_t id = 100; id <= 105; ++id) {
-    ASSERT_FALSE(fact_manager.IdIsIrrelevant(id));
-  }
-
-  for (uint32_t id = 106; id <= 111; ++id) {
-    ASSERT_TRUE(fact_manager.IdIsIrrelevant(id));
-  }
-
-  std::string after_transformation = R"(
+TEST(TransformationAddConstantScalarTest, Apply) {
+  std::string reference_shader = R"(
                OpCapability Shader
+               OpCapability Int64
+               OpCapability Float64
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %8 "x"
-               OpName %12 "y"
-               OpName %16 "z"
-               OpDecorate %8 RelaxedPrecision
-               OpDecorate %12 RelaxedPrecision
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeInt 32 1
-          %7 = OpTypePointer Function %6
-          %9 = OpConstant %6 1
-         %10 = OpTypeInt 32 0
-         %11 = OpTypePointer Function %10
-         %13 = OpConstant %10 2
-         %14 = OpTypeFloat 32
-         %15 = OpTypePointer Function %14
-         %17 = OpConstant %14 3
-        %100 = OpConstant %6 1
-        %101 = OpConstant %6 10
-        %102 = OpConstant %10 2
-        %103 = OpConstant %10 20
-        %104 = OpConstant %14 3
-        %105 = OpConstant %14 30
-        %106 = OpConstant %6 1
-        %107 = OpConstant %6 10
-        %108 = OpConstant %10 2
-        %109 = OpConstant %10 20
-        %110 = OpConstant %14 3
-        %111 = OpConstant %14 30
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-          %8 = OpVariable %7 Function
-         %12 = OpVariable %11 Function
-         %16 = OpVariable %15 Function
-               OpStore %8 %9
-               OpStore %12 %13
-               OpStore %16 %17
+               OpEntryPoint Vertex %17 "main"
+
+; Types
+
+  ; 32-bit types
+          %2 = OpTypeInt 32 0
+          %3 = OpTypeInt 32 1
+          %4 = OpTypeFloat 32
+
+  ; 64-bit types
+          %5 = OpTypeInt 64 0
+          %6 = OpTypeInt 64 1
+          %7 = OpTypeFloat 64
+
+          %8 = OpTypePointer Private %2
+          %9 = OpTypeVoid
+         %10 = OpTypeFunction %9
+
+; Constants
+
+  ; 32-bit constants
+         %11 = OpConstant %2 1
+         %12 = OpConstant %3 2
+         %13 = OpConstant %4 3
+
+  ; 64-bit constants
+         %14 = OpConstant %5 1
+         %15 = OpConstant %6 2
+         %16 = OpConstant %7 3
+
+; main function
+         %17 = OpFunction %9 None %10
+         %18 = OpLabel
                OpReturn
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  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);
+  // Adds 32-bit unsigned integer (1 logical operand with 1 word).
+  auto transformation = TransformationAddConstantScalar(19, 2, {4}, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  auto* constant_instruction = context->get_def_use_mgr()->GetDef(19);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds 32-bit signed integer (1 logical operand with 1 word).
+  transformation = TransformationAddConstantScalar(20, 3, {5}, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(20);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds 32-bit float (1 logical operand with 1 word).
+  transformation = TransformationAddConstantScalar(
+      21, 4, {0b01000000110000000000000000000000}, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(21);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds 64-bit unsigned integer (1 logical operand with 2 words).
+  transformation = TransformationAddConstantScalar(22, 5, {7, 0}, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(22);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds 64-bit signed integer (1 logical operand with 2 words).
+  transformation = TransformationAddConstantScalar(23, 6, {8, 0}, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(23);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds 64-bit float (1 logical operand with 2 words).
+  transformation = TransformationAddConstantScalar(
+      24, 7, {0, 0b01000000001000100000000000000000}, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(24);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds irrelevant 32-bit unsigned integer (1 logical operand with 1 word).
+  transformation = TransformationAddConstantScalar(25, 2, {10}, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(25);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds irrelevant 32-bit signed integer (1 logical operand with 1 word).
+  transformation = TransformationAddConstantScalar(26, 3, {11}, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(26);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds irrelevant 32-bit float (1 logical operand with 1 word).
+  transformation = TransformationAddConstantScalar(
+      27, 4, {0b01000001010000000000000000000000}, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(27);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds irrelevant 64-bit unsigned integer (1 logical operand with 2 words).
+  transformation = TransformationAddConstantScalar(28, 5, {13, 0}, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(28);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds irrelevant 64-bit signed integer (1 logical operand with 2 words).
+  transformation = TransformationAddConstantScalar(29, 6, {14, 0}, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(29);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds irrelevant 64-bit float (1 logical operand with 2 words).
+  transformation = TransformationAddConstantScalar(
+      30, 7, {0, 0b01000000001011100000000000000000}, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  constant_instruction = context->get_def_use_mgr()->GetDef(30);
+  EXPECT_EQ(constant_instruction->NumInOperands(), 1);
+  EXPECT_EQ(constant_instruction->NumInOperandWords(), 2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  for (uint32_t result_id = 19; result_id <= 24; ++result_id) {
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->IdIsIrrelevant(result_id));
+  }
+
+  for (uint32_t result_id = 25; result_id <= 30; ++result_id) {
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->IdIsIrrelevant(result_id));
+  }
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %17 "main"
+
+; Types
+
+  ; 32-bit types
+          %2 = OpTypeInt 32 0
+          %3 = OpTypeInt 32 1
+          %4 = OpTypeFloat 32
+
+  ; 64-bit types
+          %5 = OpTypeInt 64 0
+          %6 = OpTypeInt 64 1
+          %7 = OpTypeFloat 64
+
+          %8 = OpTypePointer Private %2
+          %9 = OpTypeVoid
+         %10 = OpTypeFunction %9
+
+; Constants
+
+  ; 32-bit constants
+         %11 = OpConstant %2 1
+         %12 = OpConstant %3 2
+         %13 = OpConstant %4 3
+
+  ; 64-bit constants
+         %14 = OpConstant %5 1
+         %15 = OpConstant %6 2
+         %16 = OpConstant %7 3
+
+  ; added constants
+         %19 = OpConstant %2 4
+         %20 = OpConstant %3 5
+         %21 = OpConstant %4 6
+         %22 = OpConstant %5 7
+         %23 = OpConstant %6 8
+         %24 = OpConstant %7 9
+         %25 = OpConstant %2 10
+         %26 = OpConstant %3 11
+         %27 = OpConstant %4 12
+         %28 = OpConstant %5 13
+         %29 = OpConstant %6 14
+         %30 = OpConstant %7 15
+
+; main function
+         %17 = OpFunction %9 None %10
+         %18 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_copy_memory_test.cpp b/test/fuzz/transformation_add_copy_memory_test.cpp
index 66a15f4..642a556 100644
--- a/test/fuzz/transformation_add_copy_memory_test.cpp
+++ b/test/fuzz/transformation_add_copy_memory_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_copy_memory.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -79,6 +82,7 @@
                OpSelectionMerge %29 None
                OpBranchConditional %27 %28 %31
          %28 = OpLabel
+         %89 = OpCopyObject %18 %19
                OpBranch %29
          %31 = OpLabel
                OpBranch %29
@@ -137,99 +141,97 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Target id is not fresh (59).
   ASSERT_FALSE(TransformationAddCopyMemory(
                    MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 59, 19,
                    SpvStorageClassPrivate, 20)
                    .IsApplicable(context.get(), transformation_context));
 
-  // Instruction descriptor is invalid (id 89 is undefined).
+  // Instruction descriptor is invalid (id 90 is undefined).
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(89, SpvOpVariable, 0), 89, 19,
+                   MakeInstructionDescriptor(90, SpvOpVariable, 0), 90, 19,
                    SpvStorageClassPrivate, 20)
                    .IsApplicable(context.get(), transformation_context));
 
   // Cannot insert OpCopyMemory before OpPhi.
   ASSERT_FALSE(
       TransformationAddCopyMemory(MakeInstructionDescriptor(75, SpvOpPhi, 0),
-                                  89, 19, SpvStorageClassPrivate, 20)
+                                  90, 19, SpvStorageClassPrivate, 20)
           .IsApplicable(context.get(), transformation_context));
 
   // Source instruction is invalid.
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 76,
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 90, 76,
                    SpvStorageClassPrivate, 0)
                    .IsApplicable(context.get(), transformation_context));
 
   // Source instruction's type doesn't exist.
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 5,
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 90, 5,
                    SpvStorageClassPrivate, 0)
                    .IsApplicable(context.get(), transformation_context));
 
   // Source instruction's type is invalid.
   ASSERT_FALSE(
       TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
-                                  89, 40, SpvStorageClassPrivate, 0)
+                                  90, 40, SpvStorageClassPrivate, 0)
           .IsApplicable(context.get(), transformation_context));
 
   // Source instruction is OpUndef.
   ASSERT_FALSE(
       TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
-                                  89, 87, SpvStorageClassPrivate, 0)
+                                  90, 87, SpvStorageClassPrivate, 0)
           .IsApplicable(context.get(), transformation_context));
 
   // Source instruction is OpConstantNull.
   ASSERT_FALSE(
       TransformationAddCopyMemory(MakeInstructionDescriptor(41, SpvOpLoad, 0),
-                                  89, 88, SpvStorageClassPrivate, 0)
+                                  90, 88, SpvStorageClassPrivate, 0)
           .IsApplicable(context.get(), transformation_context));
 
   // Storage class is invalid.
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 19,
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 90, 19,
                    SpvStorageClassWorkgroup, 20)
                    .IsApplicable(context.get(), transformation_context));
 
   // Initializer is 0.
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 19,
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 90, 19,
                    SpvStorageClassPrivate, 0)
                    .IsApplicable(context.get(), transformation_context));
 
   // Initializer has wrong type.
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 89, 19,
+                   MakeInstructionDescriptor(27, SpvOpFunctionCall, 0), 90, 19,
                    SpvStorageClassPrivate, 25)
                    .IsApplicable(context.get(), transformation_context));
 
   // Source and target instructions are in different functions.
   ASSERT_FALSE(
       TransformationAddCopyMemory(MakeInstructionDescriptor(13, SpvOpLoad, 0),
-                                  89, 19, SpvStorageClassPrivate, 20)
+                                  90, 19, SpvStorageClassPrivate, 20)
           .IsApplicable(context.get(), transformation_context));
 
   // Source instruction doesn't dominate the target instruction.
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(77, SpvOpLogicalEqual, 0), 89, 19,
+                   MakeInstructionDescriptor(77, SpvOpLogicalEqual, 0), 90, 89,
                    SpvStorageClassPrivate, 20)
                    .IsApplicable(context.get(), transformation_context));
 
   // Source and target instructions are the same.
   ASSERT_FALSE(TransformationAddCopyMemory(
-                   MakeInstructionDescriptor(19, SpvOpVariable, 0), 89, 19,
+                   MakeInstructionDescriptor(19, SpvOpVariable, 0), 90, 19,
                    SpvStorageClassPrivate, 20)
                    .IsApplicable(context.get(), transformation_context));
 
   // Correct transformations.
-  uint32_t fresh_id = 89;
+  uint32_t fresh_id = 90;
   auto descriptor = MakeInstructionDescriptor(27, SpvOpFunctionCall, 0);
   std::vector<uint32_t> source_ids = {19, 23, 26, 30, 35, 39, 68, 86};
   std::vector<uint32_t> initializers = {20, 24, 25, 25, 36, 84, 85, 20};
@@ -241,9 +243,13 @@
         storage_classes[i % storage_classes.size()], initializers[i]);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
-    ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(fresh_id));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(
+            fresh_id));
     fresh_id++;
   }
 
@@ -289,16 +295,16 @@
          %86 = OpVariable %79 Private %20
          %87 = OpUndef %79
          %88 = OpConstantNull %79
-         %89 = OpVariable %79 Private %20
-         %91 = OpVariable %78 Private %25
-         %93 = OpVariable %81 Private %36
-         %95 = OpVariable %83 Private %85
+         %90 = OpVariable %79 Private %20
+         %92 = OpVariable %78 Private %25
+         %94 = OpVariable %81 Private %36
+         %96 = OpVariable %83 Private %85
           %4 = OpFunction %2 None %3
           %5 = OpLabel
-         %96 = OpVariable %18 Function %20
-         %94 = OpVariable %38 Function %84
-         %92 = OpVariable %7 Function %25
-         %90 = OpVariable %22 Function %24
+         %97 = OpVariable %18 Function %20
+         %95 = OpVariable %38 Function %84
+         %93 = OpVariable %7 Function %25
+         %91 = OpVariable %22 Function %24
          %19 = OpVariable %18 Function
          %23 = OpVariable %22 Function
          %26 = OpVariable %7 Function
@@ -309,18 +315,19 @@
                OpStore %19 %20
                OpStore %23 %24
                OpStore %26 %25
-               OpCopyMemory %89 %19
-               OpCopyMemory %90 %23
-               OpCopyMemory %91 %26
-               OpCopyMemory %92 %30
-               OpCopyMemory %93 %35
-               OpCopyMemory %94 %39
-               OpCopyMemory %95 %68
-               OpCopyMemory %96 %86
+               OpCopyMemory %90 %19
+               OpCopyMemory %91 %23
+               OpCopyMemory %92 %26
+               OpCopyMemory %93 %30
+               OpCopyMemory %94 %35
+               OpCopyMemory %95 %39
+               OpCopyMemory %96 %68
+               OpCopyMemory %97 %86
          %27 = OpFunctionCall %6 %10 %26
                OpSelectionMerge %29 None
                OpBranchConditional %27 %28 %31
          %28 = OpLabel
+         %89 = OpCopyObject %18 %19
                OpBranch %29
          %31 = OpLabel
                OpBranch %29
@@ -379,6 +386,98 @@
   ASSERT_TRUE(IsEqual(env, expected, context.get()));
 }
 
+TEST(TransformationAddCopyMemoryTest, DisallowBufferBlockDecoration) {
+  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
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 4
+               OpDecorate %7 BufferBlock
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %10 = OpConstant %6 42
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer Uniform %7
+          %9 = OpVariable %8 Uniform
+         %50 = OpUndef %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_0;
+  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);
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(5, SpvOpReturn, 0),
+                                  100, 9, SpvStorageClassPrivate, 50)
+          .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddCopyMemoryTest, DisallowBlockDecoration) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main" %9
+               OpExecutionMode %4 LocalSize 1 1 1
+               OpSource ESSL 320
+               OpName %4 "main"
+               OpName %7 "buf"
+               OpMemberName %7 0 "a"
+               OpMemberName %7 1 "b"
+               OpName %9 ""
+               OpMemberDecorate %7 0 Offset 0
+               OpMemberDecorate %7 1 Offset 4
+               OpDecorate %7 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %10 = OpConstant %6 42
+          %7 = OpTypeStruct %6 %6
+          %8 = OpTypePointer StorageBuffer %7
+          %9 = OpVariable %8 StorageBuffer
+         %50 = OpUndef %7
+          %4 = OpFunction %2 None %3
+          %5 = 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);
+  ASSERT_FALSE(
+      TransformationAddCopyMemory(MakeInstructionDescriptor(5, SpvOpReturn, 0),
+                                  100, 9, SpvStorageClassPrivate, 50)
+          .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
\ No newline at end of file
diff --git a/test/fuzz/transformation_add_dead_block_test.cpp b/test/fuzz/transformation_add_dead_block_test.cpp
index c9be520..687f00c 100644
--- a/test/fuzz/transformation_add_dead_block_test.cpp
+++ b/test/fuzz/transformation_add_dead_block_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_dead_block.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -20,75 +23,123 @@
 namespace {
 
 TEST(TransformationAddDeadBlockTest, BasicTest) {
-  std::string shader = R"(
+  std::string reference_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
-          %6 = OpTypeBool
-          %7 = OpConstantTrue %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpBranch %8
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantTrue %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, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id 4 is already in use
-  ASSERT_FALSE(TransformationAddDeadBlock(4, 5, true)
-                   .IsApplicable(context.get(), transformation_context));
+  auto transformation = TransformationAddDeadBlock(4, 11, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // Id 7 is not a block
-  ASSERT_FALSE(TransformationAddDeadBlock(100, 7, true)
-                   .IsApplicable(context.get(), transformation_context));
+  // Id 5 is not a block
+  transformation = TransformationAddDeadBlock(14, 5, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  TransformationAddDeadBlock transformation(100, 5, true);
+  // Tests existing block not dominating its successor block.
+  transformation = TransformationAddDeadBlock(14, 8, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationAddDeadBlock(14, 9, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests existing block being an unreachable block.
+  transformation = TransformationAddDeadBlock(14, 12, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable case.
+  transformation = TransformationAddDeadBlock(14, 11, true);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
-  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100));
+  ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(14));
 
-  std::string after_transformation = R"(
+  std::string variant_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
-          %6 = OpTypeBool
-          %7 = OpConstantTrue %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpSelectionMerge %8 None
-               OpBranchConditional %7 %8 %100
-        %100 = OpLabel
-               OpBranch %8
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantTrue %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 %13 %14
+         %14 = OpLabel
+               OpBranch %13
+         %12 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
                OpReturn
                OpFunctionEnd
   )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
 TEST(TransformationAddDeadBlockTest, TargetBlockMustNotBeSelectionMerge) {
@@ -120,13 +171,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_FALSE(TransformationAddDeadBlock(100, 9, true)
                    .IsApplicable(context.get(), transformation_context));
 }
@@ -136,27 +185,31 @@
                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
-          %6 = OpTypeBool
-          %7 = OpConstantTrue %6
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+
+; Constants
+          %5 = OpConstantTrue %2
+
+; main function
+          %6 = OpFunction %3 None %4
+          %7 = OpLabel
                OpBranch %8
           %8 = OpLabel
-               OpLoopMerge %11 %12 None
-               OpBranchConditional %7 %9 %10
+               OpLoopMerge %12 %11 None
+               OpBranchConditional %5 %9 %10
           %9 = OpLabel
-               OpBranch %12
-         %10 = OpLabel
                OpBranch %11
-         %12 = OpLabel
-               OpBranch %8
+         %10 = OpLabel
+               OpBranch %12
          %11 = OpLabel
+               OpBranch %8
+         %12 = OpLabel
                OpReturn
                OpFunctionEnd
   )";
@@ -164,13 +217,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Bad because 9's successor is the loop continue target.
   ASSERT_FALSE(TransformationAddDeadBlock(100, 9, true)
                    .IsApplicable(context.get(), transformation_context));
@@ -210,13 +261,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Bad because 8 is a loop head.
   ASSERT_FALSE(TransformationAddDeadBlock(100, 8, true)
                    .IsApplicable(context.get(), transformation_context));
@@ -250,18 +299,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationAddDeadBlock transformation(100, 5, true);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100));
 
@@ -323,13 +371,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // 9 is a back edge block, so it would not be OK to add a dead block here,
   // as then both 9 and the dead block would branch to the loop header, 8.
   ASSERT_FALSE(TransformationAddDeadBlock(100, 9, true)
diff --git a/test/fuzz/transformation_add_dead_break_test.cpp b/test/fuzz/transformation_add_dead_break_test.cpp
index 19fac35..5302d8a 100644
--- a/test/fuzz/transformation_add_dead_break_test.cpp
+++ b/test/fuzz/transformation_add_dead_break_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_dead_break.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -21,8 +24,7 @@
 
 TEST(TransformationAddDeadBreakTest, BreaksOutOfSimpleIf) {
   // For a simple if-then-else, checks that some dead break scenarios are
-  // possible, and sanity-checks that some illegal scenarios are indeed not
-  // allowed.
+  // possible, and that some invalid scenarios are indeed not allowed.
 
   // The SPIR-V for this test is adapted from the following GLSL, by separating
   // some assignments into their own basic blocks, and adding constants for true
@@ -98,12 +100,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   const uint32_t merge_block = 16;
 
   // These are all possibilities.
@@ -152,33 +153,45 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation6.IsApplicable(context.get(), transformation_context));
-  transformation6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -339,13 +352,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // The header and merge blocks
   const uint32_t header_inner = 34;
   const uint32_t merge_inner = 23;
@@ -433,43 +444,59 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation6.IsApplicable(context.get(), transformation_context));
-  transformation6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation7.IsApplicable(context.get(), transformation_context));
-  transformation7.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation7, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation8.IsApplicable(context.get(), transformation_context));
-  transformation8.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation8, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -702,13 +729,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // The header and merge blocks
   const uint32_t header_outer_if = 5;
   const uint32_t merge_outer_if = 16;
@@ -819,53 +844,73 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation6.IsApplicable(context.get(), transformation_context));
-  transformation6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation7.IsApplicable(context.get(), transformation_context));
-  transformation7.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation7, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation8.IsApplicable(context.get(), transformation_context));
-  transformation8.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation8, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation9.IsApplicable(context.get(), transformation_context));
-  transformation9.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation9, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation10.IsApplicable(context.get(), transformation_context));
-  transformation10.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation10, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1124,13 +1169,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // The header and merge blocks
   const uint32_t header_do_while = 6;
   const uint32_t merge_do_while = 8;
@@ -1246,38 +1289,52 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation6.IsApplicable(context.get(), transformation_context));
-  transformation6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation7.IsApplicable(context.get(), transformation_context));
-  transformation7.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation7, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1461,13 +1518,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Not OK to break loop from its continue construct, except from the back-edge
   // block.
   ASSERT_FALSE(TransformationAddDeadBreak(13, 12, true, {})
@@ -1524,15 +1579,13 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto transformation = TransformationAddDeadBreak(18, 21, true, {});
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -1577,7 +1630,8 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
@@ -1658,13 +1712,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   const uint32_t loop_merge = 12;
   const uint32_t selection_merge = 24;
   const uint32_t in_selection_1 = 23;
@@ -1696,23 +1748,31 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1876,13 +1936,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   const uint32_t outer_loop_merge = 34;
   const uint32_t outer_loop_block = 33;
   const uint32_t inner_loop_merge = 47;
@@ -1903,13 +1961,17 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -2097,13 +2159,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Some inapplicable transformations
   // Not applicable because there is already an edge 19->20, so the OpPhis at 20
   // do not need to be updated
@@ -2142,28 +2202,38 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -2288,13 +2358,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadBreak(100, 101, false, {});
   ASSERT_FALSE(
       bad_transformation.IsApplicable(context.get(), transformation_context));
@@ -2345,13 +2413,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
   ASSERT_FALSE(
       bad_transformation.IsApplicable(context.get(), transformation_context));
@@ -2396,19 +2462,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto good_transformation = TransformationAddDeadBreak(100, 101, false, {11});
   ASSERT_TRUE(
       good_transformation.IsApplicable(context.get(), transformation_context));
 
-  good_transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(good_transformation, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -2488,19 +2554,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto good_transformation = TransformationAddDeadBreak(102, 101, false, {11});
   ASSERT_TRUE(
       good_transformation.IsApplicable(context.get(), transformation_context));
 
-  good_transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(good_transformation, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -2574,13 +2640,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadBreak(100, 101, false, {});
   ASSERT_FALSE(
       bad_transformation.IsApplicable(context.get(), transformation_context));
@@ -2635,13 +2699,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
   ASSERT_FALSE(
       bad_transformation.IsApplicable(context.get(), transformation_context));
@@ -2698,13 +2760,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
   ASSERT_FALSE(
       bad_transformation.IsApplicable(context.get(), transformation_context));
@@ -2748,13 +2808,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadBreak(102, 101, false, {});
   ASSERT_FALSE(
       bad_transformation.IsApplicable(context.get(), transformation_context));
@@ -2798,13 +2856,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Bad because 14 comes before 12 in the module, and 14 has no predecessors.
   // This means that an edge from 12 to 14 will lead to 12 dominating 14, which
   // is illegal if 12 appears after 14.
diff --git a/test/fuzz/transformation_add_dead_continue_test.cpp b/test/fuzz/transformation_add_dead_continue_test.cpp
index 07ee3b1..577f427 100644
--- a/test/fuzz/transformation_add_dead_continue_test.cpp
+++ b/test/fuzz/transformation_add_dead_continue_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_dead_continue.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -21,8 +24,8 @@
 
 TEST(TransformationAddDeadContinueTest, SimpleExample) {
   // For a simple loop, checks that some dead continue scenarios are possible,
-  // sanity-checks that some illegal scenarios are indeed not allowed, and then
-  // applies a transformation.
+  // checks that some invalid scenarios are indeed not allowed, and then applies
+  // a transformation.
 
   // The SPIR-V for this test is adapted from the following GLSL, by separating
   // some assignments into their own basic blocks, and adding constants for true
@@ -95,12 +98,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // These are all possibilities.
   ASSERT_TRUE(TransformationAddDeadContinue(11, true, {})
                   .IsApplicable(context.get(), transformation_context));
@@ -143,18 +145,24 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -368,13 +376,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   std::vector<uint32_t> good = {6, 7, 18, 20, 34, 40, 45, 46, 47, 56, 57};
   std::vector<uint32_t> bad = {5, 8, 9, 19, 21, 22, 33, 41, 58, 59, 60};
 
@@ -386,7 +392,8 @@
     const TransformationAddDeadContinue transformation(from_block, true, {});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
     ASSERT_FALSE(
         transformation.IsApplicable(context.get(), transformation_context));
   }
@@ -608,13 +615,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   std::vector<uint32_t> good = {32, 33, 46, 52, 101};
   std::vector<uint32_t> bad = {5, 34, 36, 35, 47, 49, 48};
 
@@ -626,7 +631,8 @@
     const TransformationAddDeadContinue transformation(from_block, false, {});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
     ASSERT_FALSE(
         transformation.IsApplicable(context.get(), transformation_context));
   }
@@ -819,13 +825,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   std::vector<uint32_t> bad = {5, 19, 20, 23, 31, 32, 33, 70};
 
   std::vector<uint32_t> good = {29, 30, 75};
@@ -837,12 +841,14 @@
   auto transformation1 = TransformationAddDeadContinue(29, true, {13, 21});
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
 
   auto transformation2 = TransformationAddDeadContinue(30, true, {22, 46});
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
 
   // 75 already has the continue block as a successor, so we should not provide
   // phi ids.
@@ -853,7 +859,8 @@
   auto transformation3 = TransformationAddDeadContinue(75, true, {});
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -994,13 +1001,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // This transformation is not applicable because the dead continue from the
   // loop body prevents the definition of %23 later in the loop body from
   // dominating its use in the loop's continue target.
@@ -1011,19 +1016,22 @@
   auto good_transformation_1 = TransformationAddDeadContinue(7, false, {});
   ASSERT_TRUE(good_transformation_1.IsApplicable(context.get(),
                                                  transformation_context));
-  good_transformation_1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(good_transformation_1, context.get(),
+                        &transformation_context);
 
   auto good_transformation_2 = TransformationAddDeadContinue(22, false, {});
   ASSERT_TRUE(good_transformation_2.IsApplicable(context.get(),
                                                  transformation_context));
-  good_transformation_2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(good_transformation_2, context.get(),
+                        &transformation_context);
 
   // This transformation is OK, because the definition of %21 in the loop body
   // is only used in an OpPhi in the loop's continue target.
   auto good_transformation_3 = TransformationAddDeadContinue(6, false, {11});
   ASSERT_TRUE(good_transformation_3.IsApplicable(context.get(),
                                                  transformation_context));
-  good_transformation_3.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(good_transformation_3, context.get(),
+                        &transformation_context);
 
   std::string after_transformations = R"(
                OpCapability Shader
@@ -1110,13 +1118,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // This transformation would shortcut the part of the loop body that defines
   // an id used after the loop.
   auto bad_transformation = TransformationAddDeadContinue(100, false, {});
@@ -1162,13 +1168,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // This transformation would shortcut the part of the loop body that defines
   // an id used after the loop.
   auto bad_transformation = TransformationAddDeadContinue(100, false, {});
@@ -1305,13 +1309,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // This transformation would shortcut the part of the loop body that defines
   // an id used in the continue target.
   auto bad_transformation = TransformationAddDeadContinue(165, false, {});
@@ -1375,13 +1377,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // This transformation would introduce a branch from a continue target to
   // itself.
   auto bad_transformation = TransformationAddDeadContinue(1554, true, {});
@@ -1437,13 +1437,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadContinue(299, false, {});
 
   // The continue edge would connect %299 to the previously-unreachable %236,
@@ -1501,13 +1499,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadContinue(10, false, {});
 
   // The continue edge would connect %10 to the previously-unreachable %13,
@@ -1557,13 +1553,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadContinue(110, true, {});
 
   // The continue edge would lead to the use of %200 in block %101 no longer
@@ -1606,13 +1600,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_transformation = TransformationAddDeadContinue(10, true, {});
 
   ASSERT_FALSE(
diff --git a/test/fuzz/transformation_add_early_terminator_wrapper_test.cpp b/test/fuzz/transformation_add_early_terminator_wrapper_test.cpp
new file mode 100644
index 0000000..8239e21
--- /dev/null
+++ b/test/fuzz/transformation_add_early_terminator_wrapper_test.cpp
@@ -0,0 +1,163 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_early_terminator_wrapper.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddEarlyTerminatorWrapperTest, NoVoidType) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Linkage
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpSource ESSL 320
+  )";
+
+  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);
+
+  ASSERT_FALSE(TransformationAddEarlyTerminatorWrapper(100, 101, SpvOpKill)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddEarlyTerminatorWrapperTest, NoVoidFunctionType) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Linkage
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+  )";
+
+  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);
+
+  ASSERT_FALSE(TransformationAddEarlyTerminatorWrapper(100, 101, SpvOpKill)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddEarlyTerminatorWrapperTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_terminate_invocation"
+          %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
+  )";
+
+  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);
+
+  ASSERT_FALSE(TransformationAddEarlyTerminatorWrapper(2, 101, SpvOpKill)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationAddEarlyTerminatorWrapper(100, 4, SpvOpKill)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationAddEarlyTerminatorWrapper(100, 100, SpvOpKill)
+                   .IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  ASSERT_DEATH(TransformationAddEarlyTerminatorWrapper(100, 101, SpvOpReturn)
+                   .IsApplicable(context.get(), transformation_context),
+               "Invalid opcode.");
+#endif
+
+  auto transformation1 =
+      TransformationAddEarlyTerminatorWrapper(100, 101, SpvOpKill);
+  auto transformation2 =
+      TransformationAddEarlyTerminatorWrapper(102, 103, SpvOpUnreachable);
+  auto transformation3 = TransformationAddEarlyTerminatorWrapper(
+      104, 105, SpvOpTerminateInvocation);
+
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_terminate_invocation"
+          %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
+        %100 = OpFunction %2 None %3
+        %101 = OpLabel
+               OpKill
+               OpFunctionEnd
+        %102 = OpFunction %2 None %3
+        %103 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+        %104 = OpFunction %2 None %3
+        %105 = OpLabel
+               OpTerminateInvocation
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_function_test.cpp b/test/fuzz/transformation_add_function_test.cpp
index bbd915b..d55fb93 100644
--- a/test/fuzz/transformation_add_function_test.cpp
+++ b/test/fuzz/transformation_add_function_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_function.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_message.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -39,7 +42,10 @@
   std::vector<protobufs::Instruction> result;
   const auto donor_context =
       BuildModule(env, consumer, donor, kFuzzAssembleOption);
-  assert(IsValid(env, donor_context.get()) && "The given donor must be valid.");
+  spvtools::ValidatorOptions validator_options;
+  assert(fuzzerutil::IsValidAndWellFormed(
+             donor_context.get(), validator_options, kConsoleMessageConsumer) &&
+         "The given donor must be valid.");
   for (auto& function : *donor_context->module()) {
     if (function.result_id() == function_id) {
       function.ForEachInst([&result](opt::Instruction* inst) {
@@ -142,13 +148,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationAddFunction transformation1(std::vector<protobufs::Instruction>(
       {MakeInstructionMessage(
            SpvOpFunction, 8, 13,
@@ -220,8 +224,10 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation1 = R"(
                OpCapability Shader
@@ -341,8 +347,10 @@
 
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation2 = R"(
                OpCapability Shader
@@ -491,13 +499,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // No instructions
   ASSERT_FALSE(
       TransformationAddFunction(std::vector<protobufs::Instruction>({}))
@@ -631,23 +637,25 @@
   instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {}));
   instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {}));
 
-  FactManager fact_manager1;
-  FactManager fact_manager2;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context1(&fact_manager1,
-                                                validator_options);
-  TransformationContext transformation_context2(&fact_manager2,
-                                                validator_options);
 
   const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
   const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context1(
+      MakeUnique<FactManager>(context1.get()), validator_options);
+  TransformationContext transformation_context2(
+      MakeUnique<FactManager>(context2.get()), validator_options);
 
   TransformationAddFunction add_dead_function(instructions);
   ASSERT_TRUE(
       add_dead_function.IsApplicable(context1.get(), transformation_context1));
-  add_dead_function.Apply(context1.get(), &transformation_context1);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ApplyAndCheckFreshIds(add_dead_function, context1.get(),
+                        &transformation_context1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
   // The added function should not be deemed livesafe.
   ASSERT_FALSE(
       transformation_context1.GetFactManager()->FunctionIsLivesafe(30));
@@ -731,8 +739,10 @@
                                                   loop_limiters, 0, {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(),
                                                  transformation_context2));
-  add_livesafe_function.Apply(context2.get(), &transformation_context2);
-  ASSERT_TRUE(IsValid(env, context2.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context2.get(),
+                        &transformation_context2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context2.get(), validator_options, kConsoleMessageConsumer));
   // The added function should indeed be deemed livesafe.
   ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(30));
   // All variables/parameters in the function should be deemed irrelevant,
@@ -853,23 +863,25 @@
   instructions.push_back(MakeInstructionMessage(SpvOpKill, 0, 0, {}));
   instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {}));
 
-  FactManager fact_manager1;
-  FactManager fact_manager2;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context1(&fact_manager1,
-                                                validator_options);
-  TransformationContext transformation_context2(&fact_manager2,
-                                                validator_options);
 
   const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
   const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context1(
+      MakeUnique<FactManager>(context1.get()), validator_options);
+  TransformationContext transformation_context2(
+      MakeUnique<FactManager>(context2.get()), validator_options);
 
   TransformationAddFunction add_dead_function(instructions);
   ASSERT_TRUE(
       add_dead_function.IsApplicable(context1.get(), transformation_context1));
-  add_dead_function.Apply(context1.get(), &transformation_context1);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ApplyAndCheckFreshIds(add_dead_function, context1.get(),
+                        &transformation_context1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
   // The added function should not be deemed livesafe.
   ASSERT_FALSE(
       transformation_context1.GetFactManager()->FunctionIsLivesafe(10));
@@ -914,8 +926,10 @@
                                                   {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(),
                                                  transformation_context2));
-  add_livesafe_function.Apply(context2.get(), &transformation_context2);
-  ASSERT_TRUE(IsValid(env, context2.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context2.get(),
+                        &transformation_context2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context2.get(), validator_options, kConsoleMessageConsumer));
   // The added function should indeed be deemed livesafe.
   ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(10));
   // All variables/parameters in the function should be deemed irrelevant.
@@ -1008,23 +1022,25 @@
   instructions.push_back(MakeInstructionMessage(SpvOpKill, 0, 0, {}));
   instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {}));
 
-  FactManager fact_manager1;
-  FactManager fact_manager2;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context1(&fact_manager1,
-                                                validator_options);
-  TransformationContext transformation_context2(&fact_manager2,
-                                                validator_options);
 
   const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
   const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context1(
+      MakeUnique<FactManager>(context1.get()), validator_options);
+  TransformationContext transformation_context2(
+      MakeUnique<FactManager>(context2.get()), validator_options);
 
   TransformationAddFunction add_dead_function(instructions);
   ASSERT_TRUE(
       add_dead_function.IsApplicable(context1.get(), transformation_context1));
-  add_dead_function.Apply(context1.get(), &transformation_context1);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ApplyAndCheckFreshIds(add_dead_function, context1.get(),
+                        &transformation_context1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
   // The added function should not be deemed livesafe.
   ASSERT_FALSE(
       transformation_context1.GetFactManager()->FunctionIsLivesafe(10));
@@ -1070,8 +1086,10 @@
                                                   {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(),
                                                  transformation_context2));
-  add_livesafe_function.Apply(context2.get(), &transformation_context2);
-  ASSERT_TRUE(IsValid(env, context2.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context2.get(),
+                        &transformation_context2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context2.get(), validator_options, kConsoleMessageConsumer));
   // The added function should indeed be deemed livesafe.
   ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(10));
   // All variables/parameters in the function should be deemed irrelevant.
@@ -1295,23 +1313,25 @@
   instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {}));
   instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {}));
 
-  FactManager fact_manager1;
-  FactManager fact_manager2;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context1(&fact_manager1,
-                                                validator_options);
-  TransformationContext transformation_context2(&fact_manager2,
-                                                validator_options);
 
   const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
   const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context1(
+      MakeUnique<FactManager>(context1.get()), validator_options);
+  TransformationContext transformation_context2(
+      MakeUnique<FactManager>(context2.get()), validator_options);
 
   TransformationAddFunction add_dead_function(instructions);
   ASSERT_TRUE(
       add_dead_function.IsApplicable(context1.get(), transformation_context1));
-  add_dead_function.Apply(context1.get(), &transformation_context1);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ApplyAndCheckFreshIds(add_dead_function, context1.get(),
+                        &transformation_context1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
   // The function should not be deemed livesafe
   ASSERT_FALSE(
       transformation_context1.GetFactManager()->FunctionIsLivesafe(12));
@@ -1450,8 +1470,10 @@
                                                   access_chain_clamping_info);
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(),
                                                  transformation_context2));
-  add_livesafe_function.Apply(context2.get(), &transformation_context2);
-  ASSERT_TRUE(IsValid(env, context2.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context2.get(),
+                        &transformation_context2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context2.get(), validator_options, kConsoleMessageConsumer));
   // The function should be deemed livesafe
   ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(12));
   // All variables/parameters in the function should be deemed irrelevant.
@@ -1622,26 +1644,28 @@
   instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {}));
   instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {}));
 
-  FactManager fact_manager1;
-  FactManager fact_manager2;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context1(&fact_manager1,
-                                                validator_options);
-  TransformationContext transformation_context2(&fact_manager2,
-                                                validator_options);
+
+  const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context1(
+      MakeUnique<FactManager>(context1.get()), validator_options);
+  TransformationContext transformation_context2(
+      MakeUnique<FactManager>(context2.get()), validator_options);
 
   // Mark function 6 as livesafe.
   transformation_context2.GetFactManager()->AddFactFunctionIsLivesafe(6);
 
-  const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context1.get()));
-
   TransformationAddFunction add_dead_function(instructions);
   ASSERT_TRUE(
       add_dead_function.IsApplicable(context1.get(), transformation_context1));
-  add_dead_function.Apply(context1.get(), &transformation_context1);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ApplyAndCheckFreshIds(add_dead_function, context1.get(),
+                        &transformation_context1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
   // The function should not be deemed livesafe
   ASSERT_FALSE(transformation_context1.GetFactManager()->FunctionIsLivesafe(8));
   // All variables/parameters in the function should be deemed irrelevant.
@@ -1677,8 +1701,10 @@
                                                   {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(),
                                                  transformation_context2));
-  add_livesafe_function.Apply(context2.get(), &transformation_context2);
-  ASSERT_TRUE(IsValid(env, context2.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context2.get(),
+                        &transformation_context2);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context2.get(), validator_options, kConsoleMessageConsumer));
   // The function should be deemed livesafe
   ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(8));
   // All variables/parameters in the function should be deemed irrelevant.
@@ -1722,23 +1748,25 @@
   instructions.push_back(MakeInstructionMessage(SpvOpReturn, 0, 0, {}));
   instructions.push_back(MakeInstructionMessage(SpvOpFunctionEnd, 0, 0, {}));
 
-  FactManager fact_manager1;
-  FactManager fact_manager2;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context1(&fact_manager1,
-                                                validator_options);
-  TransformationContext transformation_context2(&fact_manager2,
-                                                validator_options);
 
   const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
   const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
+
+  TransformationContext transformation_context1(
+      MakeUnique<FactManager>(context1.get()), validator_options);
+  TransformationContext transformation_context2(
+      MakeUnique<FactManager>(context2.get()), validator_options);
 
   TransformationAddFunction add_dead_function(instructions);
   ASSERT_TRUE(
       add_dead_function.IsApplicable(context1.get(), transformation_context1));
-  add_dead_function.Apply(context1.get(), &transformation_context1);
-  ASSERT_TRUE(IsValid(env, context1.get()));
+  ApplyAndCheckFreshIds(add_dead_function, context1.get(),
+                        &transformation_context1);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+      context1.get(), validator_options, kConsoleMessageConsumer));
   // The function should not be deemed livesafe
   ASSERT_FALSE(transformation_context1.GetFactManager()->FunctionIsLivesafe(8));
   // All variables/parameters in the function should be deemed irrelevant.
@@ -1853,15 +1881,12 @@
   )";
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %6 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -1877,8 +1902,10 @@
                                                   {loop_limiter_info}, 0, {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(),
                                                  transformation_context));
-  add_livesafe_function.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string expected = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -2011,15 +2038,12 @@
   )";
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %6 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -2035,8 +2059,10 @@
                                                   {loop_limiter_info}, 0, {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(),
                                                  transformation_context));
-  add_livesafe_function.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string expected = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -2167,15 +2193,12 @@
   )";
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %6 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -2191,8 +2214,10 @@
                                                   {loop_limiter_info}, 0, {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(),
                                                  transformation_context));
-  add_livesafe_function.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string expected = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -2246,7 +2271,7 @@
   ASSERT_TRUE(IsEqual(env, expected, context.get()));
 }
 
-TEST(TransformationAddFunctionTest, InfiniteLoop1) {
+TEST(TransformationAddFunctionTest, InfiniteLoop) {
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -2315,15 +2340,12 @@
   )";
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %6 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -2337,54 +2359,11 @@
   loop_limiter_info.set_logical_op_id(105);
   TransformationAddFunction add_livesafe_function(instructions, 100, 32,
                                                   {loop_limiter_info}, 0, {});
-  ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(),
-                                                 transformation_context));
-  add_livesafe_function.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(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 310
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %8 = OpTypeInt 32 1
-          %9 = OpTypePointer Function %8
-         %11 = OpConstant %8 0
-         %18 = OpConstant %8 10
-         %19 = OpTypeBool
-         %26 = OpConstantTrue %19
-         %27 = OpConstantFalse %19
-         %28 = OpTypeInt 32 0
-         %29 = OpTypePointer Function %28
-         %30 = OpConstant %28 0
-         %31 = OpConstant %28 1
-         %32 = OpConstant %28 5
-         %22 = OpConstant %8 1
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
-          %6 = OpFunction %2 None %3
-          %7 = OpLabel
-        %100 = OpVariable %29 Function %30
-         %10 = OpVariable %9 Function
-               OpStore %10 %11
-               OpBranch %12
-         %12 = OpLabel
-        %102 = OpLoad %28 %100
-        %103 = OpIAdd %28 %102 %31
-               OpStore %100 %103
-        %104 = OpUGreaterThanEqual %19 %102 %32
-               OpLoopMerge %14 %12 None
-               OpBranchConditional %104 %14 %12
-         %14 = OpLabel
-               OpReturn
-               OpFunctionEnd
-  )";
-  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+
+  // To make sure the loop's merge block is reachable, it must be dominated by
+  // the loop header.
+  ASSERT_FALSE(add_livesafe_function.IsApplicable(context.get(),
+                                                  transformation_context));
 }
 
 TEST(TransformationAddFunctionTest, UnreachableContinueConstruct) {
@@ -2473,15 +2452,12 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %6 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -2497,8 +2473,10 @@
                                                   {loop_limiter_info}, 0, {});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(),
                                                  transformation_context));
-  add_livesafe_function.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string expected = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -2639,15 +2617,12 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %8 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -2674,8 +2649,10 @@
                                              {loop_limiter_info}, 0, {});
   ASSERT_TRUE(
       with_op_phi_data.IsApplicable(context.get(), transformation_context));
-  with_op_phi_data.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(with_op_phi_data, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string expected = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -2832,15 +2809,12 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %8 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -2857,8 +2831,9 @@
                                            {loop_limiter_info}, 0, {});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string expected = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -2983,15 +2958,12 @@
   )";
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
-
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a sequence of instruction messages corresponding to function %6 in
   // |donor|.
   std::vector<protobufs::Instruction> instructions =
@@ -3001,8 +2973,10 @@
       instructions, 0, 0, {}, 0, {MakeAccessClampingInfo(17, {{100, 101}})});
   ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(),
                                                  transformation_context));
-  add_livesafe_function.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_livesafe_function, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string expected = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
diff --git a/test/fuzz/transformation_add_global_undef_test.cpp b/test/fuzz/transformation_add_global_undef_test.cpp
index 8c06db0..c3a49e4 100644
--- a/test/fuzz/transformation_add_global_undef_test.cpp
+++ b/test/fuzz/transformation_add_global_undef_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_global_undef.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -44,13 +47,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(TransformationAddGlobalUndef(4, 11).IsApplicable(
       context.get(), transformation_context));
@@ -84,9 +85,11 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_global_variable_test.cpp b/test/fuzz/transformation_add_global_variable_test.cpp
index 5c74ca0..eb958a7 100644
--- a/test/fuzz/transformation_add_global_variable_test.cpp
+++ b/test/fuzz/transformation_add_global_variable_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_global_variable.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -57,13 +60,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(
       TransformationAddGlobalVariable(4, 10, SpvStorageClassPrivate, 0, true)
@@ -145,7 +146,8 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   ASSERT_TRUE(
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
@@ -160,7 +162,8 @@
   ASSERT_FALSE(
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(105));
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -246,13 +249,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               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,
@@ -269,7 +270,8 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   ASSERT_TRUE(
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(100));
@@ -277,7 +279,8 @@
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(102));
   ASSERT_FALSE(
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -343,13 +346,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
 #ifndef NDEBUG
   ASSERT_DEATH(
       TransformationAddGlobalVariable(8, 7, SpvStorageClassWorkgroup, 50, true)
@@ -369,13 +370,15 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   ASSERT_TRUE(
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(8));
   ASSERT_FALSE(
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(10));
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_image_sample_unused_components_test.cpp b/test/fuzz/transformation_add_image_sample_unused_components_test.cpp
index fc78f9f..072378c 100644
--- a/test/fuzz/transformation_add_image_sample_unused_components_test.cpp
+++ b/test/fuzz/transformation_add_image_sample_unused_components_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_image_sample_unused_components.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -63,13 +66,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests applicable image instruction.
   auto instruction_descriptor =
       MakeInstructionDescriptor(25, SpvOpImageSampleImplicitLod, 0);
@@ -191,24 +192,22 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor =
       MakeInstructionDescriptor(25, SpvOpImageSampleImplicitLod, 0);
   auto transformation =
       TransformationAddImageSampleUnusedComponents(23, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(26, SpvOpImageSampleExplicitLod, 0);
   transformation =
       TransformationAddImageSampleUnusedComponents(24, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_local_variable_test.cpp b/test/fuzz/transformation_add_local_variable_test.cpp
index e989b33..ed57a28 100644
--- a/test/fuzz/transformation_add_local_variable_test.cpp
+++ b/test/fuzz/transformation_add_local_variable_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_local_variable.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -76,13 +79,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // A few cases of inapplicable transformations:
   // Id 4 is already in use
   ASSERT_FALSE(TransformationAddLocalVariable(4, 50, 4, 51, true)
@@ -99,7 +100,8 @@
     TransformationAddLocalVariable transformation(105, 50, 4, 51, true);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   // %104 = OpVariable %41 Function %46
@@ -107,7 +109,8 @@
     TransformationAddLocalVariable transformation(104, 41, 4, 46, false);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   // %103 = OpVariable %35 Function %38
@@ -115,7 +118,8 @@
     TransformationAddLocalVariable transformation(103, 35, 4, 38, true);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   // %102 = OpVariable %31 Function %33
@@ -123,7 +127,8 @@
     TransformationAddLocalVariable transformation(102, 31, 4, 33, false);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   // %101 = OpVariable %19 Function %29
@@ -131,7 +136,8 @@
     TransformationAddLocalVariable transformation(101, 19, 4, 29, true);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   // %100 = OpVariable %8 Function %12
@@ -139,7 +145,8 @@
     TransformationAddLocalVariable transformation(100, 8, 4, 12, false);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   ASSERT_FALSE(
diff --git a/test/fuzz/transformation_add_loop_preheader_test.cpp b/test/fuzz/transformation_add_loop_preheader_test.cpp
new file mode 100644
index 0000000..1634494
--- /dev/null
+++ b/test/fuzz/transformation_add_loop_preheader_test.cpp
@@ -0,0 +1,298 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_loop_preheader.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddLoopPreheaderTest, SimpleTest) {
+  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
+          %6 = OpTypeBool
+          %7 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %10 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %11 None
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %10 %12
+         %12 = OpLabel
+               OpLoopMerge %14 %13 None
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %7 %14 %12
+         %15 = OpLabel
+               OpLoopMerge %17 %16 None
+               OpBranch %16
+         %16 = OpLabel
+               OpBranchConditional %7 %15 %17
+         %17 = OpLabel
+               OpBranch %14
+         %14 = 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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %9 is not a loop header
+  ASSERT_FALSE(TransformationAddLoopPreheader(9, 15, {}).IsApplicable(
+      context.get(), transformation_context));
+
+  // The id %12 is not fresh
+  ASSERT_FALSE(TransformationAddLoopPreheader(10, 12, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Loop header %15 is not reachable (the only predecessor is the back-edge
+  // block)
+  ASSERT_FALSE(TransformationAddLoopPreheader(15, 100, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation1 = TransformationAddLoopPreheader(10, 20, {});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+
+  auto transformation2 = TransformationAddLoopPreheader(12, 21, {});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = 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
+          %6 = OpTypeBool
+          %7 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %21 %11 None
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %10 %21
+         %21 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpLoopMerge %14 %13 None
+               OpBranch %13
+         %13 = OpLabel
+               OpBranchConditional %7 %14 %12
+         %15 = OpLabel
+               OpLoopMerge %17 %16 None
+               OpBranch %16
+         %16 = OpLabel
+               OpBranchConditional %7 %15 %17
+         %17 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopPreheaderTest, OpPhi) {
+  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
+          %6 = OpTypeBool
+          %7 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpCopyObject %6 %7
+               OpBranch %8
+          %8 = OpLabel
+         %31 = OpPhi %6 %20 %5 %21 %9
+               OpLoopMerge %10 %9 None
+               OpBranch %9
+          %9 = OpLabel
+         %21 = OpCopyObject %6 %7
+               OpBranchConditional %7 %8 %10
+         %10 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %11 %12
+         %11 = OpLabel
+         %22 = OpCopyObject %6 %7
+               OpBranch %13
+         %12 = OpLabel
+         %23 = OpCopyObject %6 %7
+               OpBranch %13
+         %13 = OpLabel
+         %32 = OpPhi %6 %22 %11 %23 %12 %24 %14
+         %33 = OpPhi %6 %7 %11 %7 %12 %24 %14
+               OpLoopMerge %15 %14 None
+               OpBranch %14
+         %14 = OpLabel
+         %24 = OpCopyObject %6 %7
+               OpBranchConditional %7 %13 %15
+         %15 = 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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation1 = TransformationAddLoopPreheader(8, 40, {});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+
+  // Not enough ids for the OpPhi instructions are given
+  ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Not enough ids for the OpPhi instructions are given
+  ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {42})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // One of the ids is not fresh
+  ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {31, 42})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // One of the ids is repeated
+  ASSERT_FALSE(TransformationAddLoopPreheader(13, 41, {41, 42})
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation2 = TransformationAddLoopPreheader(13, 41, {42, 43});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = 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
+          %6 = OpTypeBool
+          %7 = OpConstantFalse %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpCopyObject %6 %7
+               OpBranch %40
+         %40 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %31 = OpPhi %6 %20 %40 %21 %9
+               OpLoopMerge %10 %9 None
+               OpBranch %9
+          %9 = OpLabel
+         %21 = OpCopyObject %6 %7
+               OpBranchConditional %7 %8 %10
+         %10 = OpLabel
+               OpSelectionMerge %41 None
+               OpBranchConditional %7 %11 %12
+         %11 = OpLabel
+         %22 = OpCopyObject %6 %7
+               OpBranch %41
+         %12 = OpLabel
+         %23 = OpCopyObject %6 %7
+               OpBranch %41
+         %41 = OpLabel
+         %42 = OpPhi %6 %22 %11 %23 %12
+         %43 = OpPhi %6 %7 %11 %7 %12
+               OpBranch %13
+         %13 = OpLabel
+         %32 = OpPhi %6 %42 %41 %24 %14
+         %33 = OpPhi %6 %43 %41 %24 %14
+               OpLoopMerge %15 %14 None
+               OpBranch %14
+         %14 = OpLabel
+         %24 = OpCopyObject %6 %7
+               OpBranchConditional %7 %13 %15
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp
new file mode 100644
index 0000000..d2d9b4d
--- /dev/null
+++ b/test/fuzz/transformation_add_loop_to_create_int_constant_synonym_test.cpp
@@ -0,0 +1,1149 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_loop_to_create_int_constant_synonym.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+     ConstantsNotSuitable) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+         %36 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 -1
+          %7 = OpConstant %5 0
+          %8 = OpConstant %5 1
+          %9 = OpConstant %5 2
+         %10 = OpConstant %5 5
+         %11 = OpConstant %5 10
+         %12 = OpConstant %5 20
+         %13 = OpConstant %5 33
+         %14 = OpTypeVector %5 2
+         %15 = OpConstantComposite %14 %10 %11
+         %16 = OpConstantComposite %14 %12 %12
+         %17 = OpTypeVector %5 3
+         %18 = OpConstantComposite %17 %11 %7 %11
+         %19 = OpTypeInt 64 1
+         %20 = OpConstant %19 0
+         %21 = OpConstant %19 10
+         %22 = OpTypeVector %19 2
+         %23 = OpConstantComposite %22 %21 %20
+         %24 = OpTypeFloat 32
+         %25 = OpConstant %24 0
+         %26 = OpConstant %24 5
+         %27 = OpConstant %24 10
+         %28 = OpConstant %24 20
+         %29 = OpTypeVector %24 3
+         %30 = OpConstantComposite %29 %26 %27 %26
+         %31 = OpConstantComposite %29 %28 %28 %28
+         %32 = OpConstantComposite %29 %27 %25 %27
+          %2 = OpFunction %3 None %4
+         %33 = OpLabel
+         %34 = OpCopyObject %5 %11
+               OpBranch %35
+         %35 = 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);
+  // Reminder: the first four parameters of the constructor are the constants
+  // with values for C, I, S, N respectively.
+
+  // %70 does not correspond to an id in the module.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   70, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %35 is not a constant.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   35, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %27, %28 and %26 are not integer constants, but scalar floats.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   27, 28, 26, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %32, %31 and %30 are not integer constants, but vector floats.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   32, 31, 30, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %18=(10, 0, 10) has 3 components, while %16=(20, 20) and %15=(5, 10)
+  // have 2.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   18, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %21 has bit width 64, while the width of %12 and %10 is 32.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   21, 12, 10, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %13 has component width 64, while the component width of %16 and %15 is 32.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   13, 16, 15, 9, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %21 (N) is a 64-bit integer, not 32-bit.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 21, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %7 (N) has value 0, so N <= 0.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 7, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %6 (N) has value -1, so N <= 1.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 6, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %13 (N) has value 33, so N > 32.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   7, 7, 7, 6, 13, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // C(%11)=10, I(%12)=20, S(%10)=5, N(%8)=1, so C=I-S*N does not hold, as
+  // 20-5*1=15.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   11, 12, 10, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // C(%15)=(5, 10), I(%16)=(20, 20), S(%15)=(5, 10), N(%8)=1, so C=I-S*N does
+  // not hold, as (20, 20)-1*(5, 10) = (15, 10).
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   15, 16, 15, 8, 35, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+     MissingConstantsOrBoolType) {
+  {
+    // The shader is missing the boolean type.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+         %20 = OpConstant %5 0
+          %6 = OpConstant %5 1
+          %7 = OpConstant %5 2
+          %8 = OpConstant %5 5
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpBranch %12
+         %12 = 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);
+    ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                     9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+                     .IsApplicable(context.get(), transformation_context));
+  }
+  {
+    // The shader is missing a 32-bit integer 0 constant.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+         %20 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 1
+          %7 = OpConstant %5 2
+          %8 = OpConstant %5 5
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpBranch %12
+         %12 = 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);
+    ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                     9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+                     .IsApplicable(context.get(), transformation_context));
+  }
+  {
+    // The shader is missing a 32-bit integer 1 constant.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+         %20 = OpTypeBool
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 0
+          %7 = OpConstant %5 2
+          %8 = OpConstant %5 5
+          %9 = OpConstant %5 10
+         %10 = OpConstant %5 20
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpBranch %12
+         %12 = 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);
+    ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                     9, 10, 8, 7, 12, 100, 101, 102, 103, 104, 105, 106, 107)
+                     .IsApplicable(context.get(), transformation_context));
+  }
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Simple) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+         %22 = OpPhi %7 %12 %14
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %17 %18
+         %17 = OpLabel
+         %23 = OpPhi %7 %13 %15
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %20 %19 None
+               OpBranchConditional %6 %20 %19
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpLoopMerge %27 %25 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpBranchConditional %6 %24 %27
+         %27 = 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);
+  // Block %14 has no predecessors.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 14, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %18 has more than one predecessor.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 18, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %16 is a merge block.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 16, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %25 is a continue block.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 25, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %19 has more than one predecessor.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 19, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %20 is a merge block.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 20, 100, 101, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %20 is supposed to be fresh, but it is not.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 100, 20, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %100 is used twice.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 100, 100, 102, 103, 104, 105, 106, 107)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %100 is used twice.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Only the last id (for the additional block) is optional, so the other ones
+  // cannot be 0.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 0, 101, 102, 103, 104, 105, 106, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // This transformation will create a synonym of constant %12 from a 1-block
+  // loop.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // This transformation will create a synonym of constant %12 from a 2-block
+  // loop.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 13, 11, 10, 17, 107, 108, 109, 110, 111, 112, 113, 114);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(107, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // This transformation will create a synonym of constant %12 from a 2-block
+  // loop.
+  auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 13, 11, 10, 26, 115, 116, 117, 118, 119, 120, 121, 0);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(115, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %14 %105 %101
+        %103 = OpPhi %7 %13 %14 %104 %101
+        %104 = OpISub %7 %103 %11
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %10
+               OpLoopMerge %15 %101 None
+               OpBranchConditional %106 %101 %15
+         %15 = OpLabel
+        %100 = OpPhi %7 %104 %101
+         %22 = OpPhi %7 %12 %101
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %108 %18
+        %108 = OpLabel
+        %109 = OpPhi %7 %8 %15 %112 %114
+        %110 = OpPhi %7 %13 %15 %111 %114
+               OpLoopMerge %17 %114 None
+               OpBranch %114
+        %114 = OpLabel
+        %111 = OpISub %7 %110 %11
+        %112 = OpIAdd %7 %109 %9
+        %113 = OpSLessThan %5 %112 %10
+               OpBranchConditional %113 %108 %17
+         %17 = OpLabel
+        %107 = OpPhi %7 %111 %114
+         %23 = OpPhi %7 %13 %114
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %20 %19 None
+               OpBranchConditional %6 %20 %19
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpLoopMerge %27 %25 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranch %116
+        %116 = OpLabel
+        %117 = OpPhi %7 %8 %25 %120 %116
+        %118 = OpPhi %7 %13 %25 %119 %116
+        %119 = OpISub %7 %118 %11
+        %120 = OpIAdd %7 %117 %9
+        %121 = OpSLessThan %5 %120 %10
+               OpLoopMerge %26 %116 None
+               OpBranchConditional %121 %116 %26
+         %26 = OpLabel
+        %115 = OpPhi %7 %119 %116
+               OpBranchConditional %6 %24 %27
+         %27 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+     DifferentSignednessAndVectors) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %14 5
+         %17 = OpConstant %14 10
+         %18 = OpConstant %14 20
+         %19 = OpTypeVector %7 2
+         %20 = OpTypeVector %14 2
+         %21 = OpConstantComposite %19 %12 %8
+         %22 = OpConstantComposite %20 %17 %15
+         %23 = OpConstantComposite %19 %13 %12
+         %24 = OpConstantComposite %19 %11 %11
+          %2 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpBranch %27
+         %27 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpBranch %30
+         %30 = 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);
+  // These tests check that the transformation is applicable and is applied
+  // correctly with integers, scalar and vectors, of different signedness.
+
+  // %12 is a signed integer, %18 and %16 are unsigned integers.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 18, 16, 10, 26, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %12 and %11 are signed integers, %18 is an unsigned integer.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 18, 11, 10, 27, 108, 109, 110, 111, 112, 113, 114, 0);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(108, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %17, %18 and %16 are all signed integers.
+  auto transformation3 = TransformationAddLoopToCreateIntConstantSynonym(
+      17, 18, 16, 10, 28, 115, 116, 117, 118, 119, 120, 121, 0);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(17, {}), MakeDataDescriptor(115, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %22 is an unsigned integer vector, %23 and %24 are signed integer vectors.
+  auto transformation4 = TransformationAddLoopToCreateIntConstantSynonym(
+      22, 23, 24, 10, 29, 122, 123, 124, 125, 126, 127, 128, 0);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(22, {}), MakeDataDescriptor(122, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %21, %23 and %24 are all signed integer vectors.
+  auto transformation5 = TransformationAddLoopToCreateIntConstantSynonym(
+      21, 23, 24, 10, 30, 129, 130, 131, 132, 133, 134, 135, 0);
+  ASSERT_TRUE(
+      transformation5.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(129, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+         %14 = OpTypeInt 32 0
+         %15 = OpConstant %14 0
+         %16 = OpConstant %14 5
+         %17 = OpConstant %14 10
+         %18 = OpConstant %14 20
+         %19 = OpTypeVector %7 2
+         %20 = OpTypeVector %14 2
+         %21 = OpConstantComposite %19 %12 %8
+         %22 = OpConstantComposite %20 %17 %15
+         %23 = OpConstantComposite %19 %13 %12
+         %24 = OpConstantComposite %19 %11 %11
+          %2 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %25 %105 %101
+        %103 = OpPhi %14 %18 %25 %104 %101
+        %104 = OpISub %14 %103 %16
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %10
+               OpLoopMerge %26 %101 None
+               OpBranchConditional %106 %101 %26
+         %26 = OpLabel
+        %100 = OpPhi %14 %104 %101
+               OpBranch %109
+        %109 = OpLabel
+        %110 = OpPhi %7 %8 %26 %113 %109
+        %111 = OpPhi %14 %18 %26 %112 %109
+        %112 = OpISub %14 %111 %11
+        %113 = OpIAdd %7 %110 %9
+        %114 = OpSLessThan %5 %113 %10
+               OpLoopMerge %27 %109 None
+               OpBranchConditional %114 %109 %27
+         %27 = OpLabel
+        %108 = OpPhi %14 %112 %109
+               OpBranch %116
+        %116 = OpLabel
+        %117 = OpPhi %7 %8 %27 %120 %116
+        %118 = OpPhi %14 %18 %27 %119 %116
+        %119 = OpISub %14 %118 %16
+        %120 = OpIAdd %7 %117 %9
+        %121 = OpSLessThan %5 %120 %10
+               OpLoopMerge %28 %116 None
+               OpBranchConditional %121 %116 %28
+         %28 = OpLabel
+        %115 = OpPhi %14 %119 %116
+               OpBranch %123
+        %123 = OpLabel
+        %124 = OpPhi %7 %8 %28 %127 %123
+        %125 = OpPhi %19 %23 %28 %126 %123
+        %126 = OpISub %19 %125 %24
+        %127 = OpIAdd %7 %124 %9
+        %128 = OpSLessThan %5 %127 %10
+               OpLoopMerge %29 %123 None
+               OpBranchConditional %128 %123 %29
+         %29 = OpLabel
+        %122 = OpPhi %19 %126 %123
+               OpBranch %130
+        %130 = OpLabel
+        %131 = OpPhi %7 %8 %29 %134 %130
+        %132 = OpPhi %19 %23 %29 %133 %130
+        %133 = OpISub %19 %132 %24
+        %134 = OpIAdd %7 %131 %9
+        %135 = OpSLessThan %5 %134 %10
+               OpLoopMerge %30 %130 None
+               OpBranchConditional %135 %130 %30
+         %30 = OpLabel
+        %129 = OpPhi %19 %133 %130
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, 64BitConstants) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpTypeInt 64 1
+         %12 = OpConstant %11 5
+         %13 = OpConstant %11 10
+         %14 = OpConstant %11 20
+         %15 = OpTypeVector %11 2
+         %16 = OpConstantComposite %15 %13 %13
+         %17 = OpConstantComposite %15 %14 %14
+         %18 = OpConstantComposite %15 %12 %12
+          %2 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %21
+         %21 = 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);
+  // These tests check that the transformation can be applied, and is applied
+  // correctly, to 64-bit integer (scalar and vector) constants.
+
+  // 64-bit scalar integers.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      13, 14, 12, 10, 20, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(13, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // 64-bit vector integers.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      16, 17, 18, 10, 21, 107, 108, 109, 110, 111, 112, 113, 0);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(16, {}), MakeDataDescriptor(107, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+               OpCapability Int64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpTypeInt 64 1
+         %12 = OpConstant %11 5
+         %13 = OpConstant %11 10
+         %14 = OpConstant %11 20
+         %15 = OpTypeVector %11 2
+         %16 = OpConstantComposite %15 %13 %13
+         %17 = OpConstantComposite %15 %14 %14
+         %18 = OpConstantComposite %15 %12 %12
+          %2 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %19 %105 %101
+        %103 = OpPhi %11 %14 %19 %104 %101
+        %104 = OpISub %11 %103 %12
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %10
+               OpLoopMerge %20 %101 None
+               OpBranchConditional %106 %101 %20
+         %20 = OpLabel
+        %100 = OpPhi %11 %104 %101
+               OpBranch %108
+        %108 = OpLabel
+        %109 = OpPhi %7 %8 %20 %112 %108
+        %110 = OpPhi %15 %17 %20 %111 %108
+        %111 = OpISub %15 %110 %18
+        %112 = OpIAdd %7 %109 %9
+        %113 = OpSLessThan %5 %112 %10
+               OpLoopMerge %21 %108 None
+               OpBranchConditional %113 %108 %21
+         %21 = OpLabel
+        %107 = OpPhi %15 %111 %108
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, Underflow) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 20
+         %12 = OpConstant %7 -4
+         %13 = OpTypeInt 32 0
+         %14 = OpConstant %13 214748365
+         %15 = OpConstant %13 4294967256
+          %2 = OpFunction %3 None %4
+         %16 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranch %18
+         %18 = 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);
+  // These tests check that underflows are taken into consideration when
+  // deciding if  transformation is applicable.
+
+  // Subtracting 2147483648 20 times from 32-bit integer 0 underflows 2 times
+  // and the result is equivalent to -4.
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      12, 8, 14, 11, 17, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Subtracting 20 twice from 0 underflows and gives the unsigned integer
+  // 4294967256.
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      15, 8, 11, 10, 18, 107, 108, 109, 110, 111, 112, 113, 0);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(15, {}), MakeDataDescriptor(107, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 20
+         %12 = OpConstant %7 -4
+         %13 = OpTypeInt 32 0
+         %14 = OpConstant %13 214748365
+         %15 = OpConstant %13 4294967256
+          %2 = OpFunction %3 None %4
+         %16 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %7 %8 %16 %105 %101
+        %103 = OpPhi %7 %8 %16 %104 %101
+        %104 = OpISub %7 %103 %14
+        %105 = OpIAdd %7 %102 %9
+        %106 = OpSLessThan %5 %105 %11
+               OpLoopMerge %17 %101 None
+               OpBranchConditional %106 %101 %17
+         %17 = OpLabel
+        %100 = OpPhi %7 %104 %101
+               OpBranch %108
+        %108 = OpLabel
+        %109 = OpPhi %7 %8 %17 %112 %108
+        %110 = OpPhi %7 %8 %17 %111 %108
+        %111 = OpISub %7 %110 %11
+        %112 = OpIAdd %7 %109 %9
+        %113 = OpSLessThan %5 %112 %10
+               OpLoopMerge %18 %108 None
+               OpBranchConditional %113 %108 %18
+         %18 = OpLabel
+        %107 = OpPhi %7 %111 %108
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest,
+     InapplicableDueToDeadBlockOrIrrelevantId) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpConstant %7 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %7 5
+         %12 = OpConstant %7 10
+         %13 = OpConstant %7 20
+       %1010 = OpConstant %7 2
+       %1011 = OpConstant %7 5
+       %1012 = OpConstant %7 10
+       %1013 = OpConstant %7 20
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %16 %15
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %17
+         %17 = 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);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(15);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(1010);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(1011);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(1012);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(1013);
+  // Bad because the block before which the loop would be inserted is dead.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 10, 15, 100, 101, 102, 103, 104, 105, 106, 0)
+                   .IsApplicable(context.get(), transformation_context));
+  // OK
+  ASSERT_TRUE(TransformationAddLoopToCreateIntConstantSynonym(
+                  12, 13, 11, 10, 17, 100, 101, 102, 103, 104, 105, 106, 0)
+                  .IsApplicable(context.get(), transformation_context));
+  // Bad because in each case one of the constants involved is irrelevant.
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   1012, 13, 11, 10, 17, 100, 101, 102, 103, 104, 105, 106, 0)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 1013, 11, 10, 17, 100, 101, 102, 103, 104, 105, 106, 0)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 1011, 10, 17, 100, 101, 102, 103, 104, 105, 106, 0)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationAddLoopToCreateIntConstantSynonym(
+                   12, 13, 11, 1010, 17, 100, 101, 102, 103, 104, 105, 106, 0)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddLoopToCreateIntConstantSynonymTest, InserBeforeOpSwitch) {
+  // Checks that it is acceptable for a loop to be added before a target of an
+  // OpSwitch instruction.
+  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 = OpConstant %6 0
+         %20 = OpConstant %6 1
+         %21 = OpConstant %6 2
+         %22 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %10 None
+               OpSwitch %7 %9 0 %8
+          %9 = OpLabel
+               OpBranch %10
+          %8 = OpLabel
+               OpBranch %10
+         %10 = 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);
+
+  auto transformation1 = TransformationAddLoopToCreateIntConstantSynonym(
+      20, 21, 20, 20, 9, 100, 101, 102, 103, 104, 105, 106, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(20, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation2 = TransformationAddLoopToCreateIntConstantSynonym(
+      20, 21, 20, 20, 8, 200, 201, 202, 203, 204, 205, 206, 0);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(20, {}), MakeDataDescriptor(200, {})));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = 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 = OpConstant %6 0
+         %20 = OpConstant %6 1
+         %21 = OpConstant %6 2
+         %22 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %10 None
+               OpSwitch %7 %101 0 %201
+        %101 = OpLabel
+        %102 = OpPhi %6 %7 %5 %105 %101
+        %103 = OpPhi %6 %21 %5 %104 %101
+        %104 = OpISub %6 %103 %20
+        %105 = OpIAdd %6 %102 %20
+        %106 = OpSLessThan %22 %105 %20
+               OpLoopMerge %9 %101 None
+               OpBranchConditional %106 %101 %9
+          %9 = OpLabel
+        %100 = OpPhi %6 %104 %101
+               OpBranch %10
+        %201 = OpLabel
+        %202 = OpPhi %6 %7 %5 %205 %201
+        %203 = OpPhi %6 %21 %5 %204 %201
+        %204 = OpISub %6 %203 %20
+        %205 = OpIAdd %6 %202 %20
+        %206 = OpSLessThan %22 %205 %20
+               OpLoopMerge %8 %201 None
+               OpBranchConditional %206 %201 %8
+          %8 = OpLabel
+        %200 = OpPhi %6 %204 %201
+               OpBranch %10
+         %10 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_no_contraction_decoration_test.cpp b/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
index 46841a5..4fc9d2d 100644
--- a/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
+++ b/test/fuzz/transformation_add_no_contraction_decoration_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -92,12 +95,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Invalid: 200 is not an id
   ASSERT_FALSE(TransformationAddNoContractionDecoration(200).IsApplicable(
       context.get(), transformation_context));
@@ -114,8 +116,10 @@
     TransformationAddNoContractionDecoration transformation(result_id);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_add_opphi_synonym_test.cpp b/test/fuzz/transformation_add_opphi_synonym_test.cpp
new file mode 100644
index 0000000..3501f8e
--- /dev/null
+++ b/test/fuzz/transformation_add_opphi_synonym_test.cpp
@@ -0,0 +1,488 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_opphi_synonym.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+protobufs::Fact MakeSynonymFact(uint32_t first, uint32_t second) {
+  protobufs::FactDataSynonym data_synonym_fact;
+  *data_synonym_fact.mutable_data1() = MakeDataDescriptor(first, {});
+  *data_synonym_fact.mutable_data2() = MakeDataDescriptor(second, {});
+  protobufs::Fact result;
+  *result.mutable_data_synonym_fact() = data_synonym_fact;
+  return result;
+}
+
+// Adds synonym facts to the fact manager.
+void SetUpIdSynonyms(FactManager* fact_manager) {
+  fact_manager->MaybeAddFact(MakeSynonymFact(11, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(13, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(14, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(19, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(20, 9));
+  fact_manager->MaybeAddFact(MakeSynonymFact(10, 21));
+}
+
+TEST(TransformationAddOpPhiSynonymTest, Inapplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeInt 32 0
+         %22 = OpTypePointer Function %7
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %8 1
+          %2 = OpFunction %3 None %4
+         %12 = OpLabel
+         %23 = OpVariable %22 Function
+         %13 = OpCopyObject %7 %9
+         %14 = OpCopyObject %8 %11
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %17 %18
+         %17 = OpLabel
+         %19 = OpCopyObject %7 %13
+         %20 = OpCopyObject %8 %14
+         %21 = OpCopyObject %7 %10
+               OpBranch %16
+         %18 = OpLabel
+         %24 = OpCopyObject %22 %23
+               OpBranch %16
+         %16 = 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);
+  SetUpIdSynonyms(transformation_context.GetFactManager());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(23, 24));
+
+  // %13 is not a block label.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %12 does not have a predecessor.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(12, {{}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Not all predecessors of %16 (%17 and %18) are considered in the map.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %30 does not exist in the module.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{30, 19}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %20 is not a block label.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{20, 19}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %15 is not the id of one of the predecessors of the block.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{15, 19}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %30 does not exist in the module.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 30}, {18, 13}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %19 and %10 are not synonymous.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 10}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %19 and %14 do not have the same type.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 14}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %19 is not available at the end of %18.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 19}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %21 is not a fresh id.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 9}, {18, 9}}}, 21)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %23 and %24 have pointer id.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(16, {{{17, 23}, {18, 24}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddOpPhiSynonymTest, Apply) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeInt 32 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %8 1
+          %2 = OpFunction %3 None %4
+         %12 = OpLabel
+         %13 = OpCopyObject %7 %9
+         %14 = OpCopyObject %8 %11
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %17 %18
+         %17 = OpLabel
+         %19 = OpCopyObject %7 %13
+         %20 = OpCopyObject %8 %14
+         %21 = OpCopyObject %7 %10
+               OpBranch %16
+         %18 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranchConditional %6 %25 %23
+         %25 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %6 %27 %26
+         %27 = OpLabel
+         %28 = OpCopyObject %7 %13
+               OpBranch %23
+         %26 = OpLabel
+               OpSelectionMerge %29 None
+               OpBranchConditional %6 %29 %24
+         %29 = OpLabel
+         %30 = OpCopyObject %7 %13
+               OpBranch %23
+         %24 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %6 %31 %31
+         %31 = 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);
+  SetUpIdSynonyms(transformation_context.GetFactManager());
+
+  // Add some further synonym facts.
+  transformation_context.GetFactManager()->MaybeAddFact(MakeSynonymFact(28, 9));
+  transformation_context.GetFactManager()->MaybeAddFact(MakeSynonymFact(30, 9));
+
+  auto transformation1 = TransformationAddOpPhiSynonym(17, {{{15, 13}}}, 100);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(9, {})));
+
+  auto transformation2 =
+      TransformationAddOpPhiSynonym(16, {{{17, 19}, {18, 13}}}, 101);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(101, {}), MakeDataDescriptor(9, {})));
+
+  auto transformation3 =
+      TransformationAddOpPhiSynonym(23, {{{22, 13}, {27, 28}, {29, 30}}}, 102);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(102, {}), MakeDataDescriptor(9, {})));
+
+  auto transformation4 = TransformationAddOpPhiSynonym(31, {{{23, 13}}}, 103);
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(103, {}), MakeDataDescriptor(9, {})));
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeInt 32 0
+          %9 = OpConstant %7 1
+         %10 = OpConstant %7 2
+         %11 = OpConstant %8 1
+          %2 = OpFunction %3 None %4
+         %12 = OpLabel
+         %13 = OpCopyObject %7 %9
+         %14 = OpCopyObject %8 %11
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %6 %17 %18
+         %17 = OpLabel
+        %100 = OpPhi %7 %13 %15
+         %19 = OpCopyObject %7 %13
+         %20 = OpCopyObject %8 %14
+         %21 = OpCopyObject %7 %10
+               OpBranch %16
+         %18 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+        %101 = OpPhi %7 %19 %17 %13 %18
+               OpBranch %22
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranchConditional %6 %25 %23
+         %25 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %6 %27 %26
+         %27 = OpLabel
+         %28 = OpCopyObject %7 %13
+               OpBranch %23
+         %26 = OpLabel
+               OpSelectionMerge %29 None
+               OpBranchConditional %6 %29 %24
+         %29 = OpLabel
+         %30 = OpCopyObject %7 %13
+               OpBranch %23
+         %24 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+        %102 = OpPhi %7 %13 %22 %28 %27 %30 %29
+               OpSelectionMerge %31 None
+               OpBranchConditional %6 %31 %31
+         %31 = OpLabel
+        %103 = OpPhi %7 %13 %23
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationAddOpPhiSynonymTest, VariablePointers) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypePointer Workgroup %8
+          %3 = OpVariable %10 Workgroup
+          %2 = OpFunction %4 None %5
+         %11 = OpLabel
+         %12 = OpVariable %9 Function
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %14 %13
+         %14 = OpLabel
+         %15 = OpCopyObject %10 %3
+         %16 = OpCopyObject %9 %12
+               OpBranch %13
+         %13 = 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);
+  // Declare synonyms
+  transformation_context.GetFactManager()->MaybeAddFact(MakeSynonymFact(3, 15));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(12, 16));
+
+  // Remove the VariablePointers capability.
+  context.get()->get_feature_mgr()->RemoveCapability(
+      SpvCapabilityVariablePointers);
+
+  // The VariablePointers capability is required to add an OpPhi instruction of
+  // pointer type.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Add the VariablePointers capability back.
+  context.get()->get_feature_mgr()->AddCapability(
+      SpvCapabilityVariablePointers);
+
+  // If the ids have pointer type, the storage class must be Workgroup or
+  // StorageBuffer, but it is Function in this case.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(13, {{{11, 12}, {14, 16}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation =
+      TransformationAddOpPhiSynonym(13, {{{11, 3}, {14, 15}}}, 100);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypePointer Workgroup %8
+          %3 = OpVariable %10 Workgroup
+          %2 = OpFunction %4 None %5
+         %11 = OpLabel
+         %12 = OpVariable %9 Function
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %14 %13
+         %14 = OpLabel
+         %15 = OpCopyObject %10 %3
+         %16 = OpCopyObject %9 %12
+               OpBranch %13
+         %13 = OpLabel
+        %100 = OpPhi %10 %3 %11 %15 %14
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationAddOpPhiSynonymTest, DeadBlock) {
+  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 2
+         %10 = OpTypeBool
+         %11 = OpConstantFalse %10
+         %15 = OpConstant %6 0
+         %50 = OpConstant %6 0
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpSelectionMerge %13 None
+               OpBranchConditional %11 %12 %13
+         %12 = OpLabel
+         %14 = OpLoad %6 %8
+         %16 = OpIEqual %10 %14 %15
+               OpSelectionMerge %18 None
+               OpBranchConditional %16 %17 %40
+         %17 = OpLabel
+               OpBranch %18
+         %40 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %13
+         %13 = 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);
+  // Dead blocks
+  transformation_context.GetFactManager()->AddFactBlockIsDead(12);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(17);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(18);
+
+  // Declare synonym
+  ASSERT_TRUE(transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(15, 50)));
+
+  // Bad because the block 18 is dead.
+  ASSERT_FALSE(TransformationAddOpPhiSynonym(18, {{{17, 15}, {40, 50}}}, 100)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_parameter_test.cpp b/test/fuzz/transformation_add_parameter_test.cpp
index 6593d00..7b2a15f 100644
--- a/test/fuzz/transformation_add_parameter_test.cpp
+++ b/test/fuzz/transformation_add_parameter_test.cpp
@@ -14,13 +14,15 @@
 
 #include "source/fuzz/transformation_add_parameter.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
 namespace fuzz {
 namespace {
 
-TEST(TransformationAddParameterTest, BasicTest) {
+TEST(TransformationAddParameterTest, NonPointerBasicTest) {
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -32,65 +34,136 @@
           %2 = OpTypeVoid
           %7 = OpTypeBool
          %11 = OpTypeInt 32 1
+         %16 = OpTypeFloat 32
           %3 = OpTypeFunction %2
           %6 = OpTypeFunction %7 %7
           %8 = OpConstant %11 23
          %12 = OpConstantTrue %7
+         %15 = OpTypeFunction %2 %16
+         %24 = OpTypeFunction %2 %16 %7
+         %31 = OpTypeStruct %7 %11
+         %32 = OpConstant %16 23
+         %33 = OpConstantComposite %31 %12 %8
+         %41 = OpTypeStruct %11 %16
+         %42 = OpConstantComposite %41 %8 %32
+         %43 = OpTypeFunction %2 %41
+         %44 = OpTypeFunction %2 %41 %7
           %4 = OpFunction %2 None %3
           %5 = OpLabel
          %13 = OpFunctionCall %7 %9 %12
                OpReturn
                OpFunctionEnd
+
+          ; adjust type of the function in-place
           %9 = OpFunction %7 None %6
          %14 = OpFunctionParameter %7
          %10 = OpLabel
                OpReturnValue %12
                OpFunctionEnd
+
+         ; reuse an existing function type
+         %17 = OpFunction %2 None %15
+         %18 = OpFunctionParameter %16
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %2 None %15
+         %21 = OpFunctionParameter %16
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %25 = OpFunction %2 None %24
+         %26 = OpFunctionParameter %16
+         %27 = OpFunctionParameter %7
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         ; create a new function type
+         %29 = OpFunction %2 None %3
+         %30 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         ; don't adjust the type of the function if it creates a duplicate
+         %34 = OpFunction %2 None %43
+         %35 = OpFunctionParameter %41
+         %36 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %37 = OpFunction %2 None %44
+         %38 = OpFunctionParameter %41
+         %39 = OpFunctionParameter %7
+         %40 = OpLabel
+               OpReturn
+               OpFunctionEnd
   )";
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Can't modify entry point function.
-  ASSERT_FALSE(TransformationAddParameter(4, 15, 12, 16)
+  ASSERT_FALSE(TransformationAddParameter(4, 60, 7, {{}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
-  // There is no function with result id 29.
-  ASSERT_FALSE(TransformationAddParameter(29, 15, 8, 16)
+  // There is no function with result id 60.
+  ASSERT_FALSE(TransformationAddParameter(60, 60, 11, {{}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
   // Parameter id is not fresh.
-  ASSERT_FALSE(TransformationAddParameter(9, 14, 8, 16)
+  ASSERT_FALSE(TransformationAddParameter(9, 14, 11, {{{13, 8}}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
   // Function type id is not fresh.
-  ASSERT_FALSE(TransformationAddParameter(9, 15, 8, 14)
+  ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 8}}}, 14)
                    .IsApplicable(context.get(), transformation_context));
 
   // Function type id and parameter type id are equal.
-  ASSERT_FALSE(TransformationAddParameter(9, 15, 8, 15)
+  ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 8}}}, 60)
                    .IsApplicable(context.get(), transformation_context));
 
   // Parameter's initializer doesn't exist.
-  ASSERT_FALSE(TransformationAddParameter(9, 15, 15, 16)
+  ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 60}}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
-  // Correct transformation.
-  TransformationAddParameter correct(9, 15, 8, 16);
-  ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
-  correct.Apply(context.get(), &transformation_context);
-
-  // The module remains valid.
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  ASSERT_TRUE(fact_manager.IdIsIrrelevant(15));
+  // Correct transformations.
+  {
+    TransformationAddParameter correct(9, 60, 11, {{{13, 8}}}, 61);
+    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(60));
+  }
+  {
+    TransformationAddParameter correct(17, 62, 7, {{}}, 63);
+    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(62));
+  }
+  {
+    TransformationAddParameter correct(29, 64, 31, {{}}, 65);
+    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(64));
+  }
+  {
+    TransformationAddParameter correct(34, 66, 7, {{}}, 67);
+    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(66));
+  }
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -103,26 +176,931 @@
           %2 = OpTypeVoid
           %7 = OpTypeBool
          %11 = OpTypeInt 32 1
+         %16 = OpTypeFloat 32
           %3 = OpTypeFunction %2
           %8 = OpConstant %11 23
          %12 = OpConstantTrue %7
+         %15 = OpTypeFunction %2 %16
+         %24 = OpTypeFunction %2 %16 %7
+         %31 = OpTypeStruct %7 %11
+         %32 = OpConstant %16 23
+         %33 = OpConstantComposite %31 %12 %8
+         %41 = OpTypeStruct %11 %16
+         %42 = OpConstantComposite %41 %8 %32
+         %44 = OpTypeFunction %2 %41 %7
           %6 = OpTypeFunction %7 %7 %11
+         %65 = OpTypeFunction %2 %31
           %4 = OpFunction %2 None %3
           %5 = OpLabel
          %13 = OpFunctionCall %7 %9 %12 %8
                OpReturn
                OpFunctionEnd
+
+          ; adjust type of the function in-place
           %9 = OpFunction %7 None %6
          %14 = OpFunctionParameter %7
-         %15 = OpFunctionParameter %11
+         %60 = OpFunctionParameter %11
          %10 = OpLabel
                OpReturnValue %12
                OpFunctionEnd
-  )";
 
+         ; reuse an existing function type
+         %17 = OpFunction %2 None %24
+         %18 = OpFunctionParameter %16
+         %62 = OpFunctionParameter %7
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %2 None %15
+         %21 = OpFunctionParameter %16
+         %22 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %25 = OpFunction %2 None %24
+         %26 = OpFunctionParameter %16
+         %27 = OpFunctionParameter %7
+         %28 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         ; create a new function type
+         %29 = OpFunction %2 None %65
+         %64 = OpFunctionParameter %31
+         %30 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         ; don't adjust the type of the function if it creates a duplicate
+         %34 = OpFunction %2 None %44
+         %35 = OpFunctionParameter %41
+         %66 = OpFunctionParameter %7
+         %36 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %37 = OpFunction %2 None %44
+         %38 = OpFunctionParameter %41
+         %39 = OpFunctionParameter %7
+         %40 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
   ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
 }
 
+TEST(TransformationAddParameterTest, NonPointerNotApplicableTest) {
+  // This types handles case of adding a new parameter of a non-pointer type
+  // where the transformation is not applicable.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %24 "f1"
+               OpName %27 "f2"
+               OpName %30 "i1"
+               OpName %31 "i2"
+               OpName %32 "param"
+               OpName %35 "i3"
+               OpName %36 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %8 %9
+         %18 = OpConstant %8 2
+         %22 = OpTypeFloat 32
+         %23 = OpTypePointer Private %22
+         %24 = OpVariable %23 Private
+         %25 = OpConstant %22 1
+         %26 = OpTypePointer Function %22
+         %28 = OpConstant %22 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %27 = OpVariable %26 Function
+         %30 = OpVariable %9 Function
+         %31 = OpVariable %9 Function
+         %32 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+               OpStore %24 %25
+               OpStore %27 %28
+         %29 = OpFunctionCall %2 %6
+               OpStore %30 %18
+         %33 = OpLoad %8 %30
+               OpStore %32 %33
+         %34 = OpFunctionCall %8 %12 %32
+               OpStore %31 %34
+         %37 = OpLoad %8 %31
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36
+               OpStore %35 %38
+         ; %39 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %17 = OpLoad %8 %11
+         %19 = OpIAdd %8 %17 %18
+               OpReturnValue %19
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = 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: Id 19 is not available in the caller that has id 34.
+  TransformationAddParameter transformation_bad_1(12, 50, 8,
+                                                  {{{34, 19}, {38, 19}}}, 51);
+
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Id 8 does not have a type.
+  TransformationAddParameter transformation_bad_2(12, 50, 8,
+                                                  {{{34, 8}, {38, 8}}}, 51);
+
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Types of id 25 and id 18 are different.
+  TransformationAddParameter transformation_bad_3(12, 50, 22,
+                                                  {{{34, 25}, {38, 18}}}, 51);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Function with id 14 does not have any callers.
+  // Bad: Id 18 is not a vaild type.
+  TransformationAddParameter transformation_bad_4(14, 50, 18, {{}}, 51);
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+
+  // Function with id 14 does not have any callers.
+  // Bad:  Id 3 refers to OpTypeVoid, which is not supported.
+  TransformationAddParameter transformation_bad_6(14, 50, 3, {{}}, 51);
+  ASSERT_FALSE(
+      transformation_bad_6.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddParameterTest, PointerFunctionTest) {
+  // This types handles case of adding a new parameter of a pointer type with
+  // storage class Function.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %8 %9
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Output %26
+         %61 = OpVariable %60 Output
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               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: Pointer of id 61 has storage class Output, which is not supported.
+  TransformationAddParameter transformation_bad_1(12, 50, 60,
+                                                  {{{38, 61}, {42, 61}}}, 51);
+
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Good: Local variable of id 31 is defined in the caller (main).
+  TransformationAddParameter transformation_good_1(12, 50, 30,
+                                                   {{{38, 31}, {42, 31}}}, 51);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Good: Local variable of id 34 is defined in the caller (main).
+  TransformationAddParameter transformation_good_2(14, 52, 9, {{{43, 34}}}, 53);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Good: Local variable of id 39 is defined in the caller (main).
+  TransformationAddParameter transformation_good_3(6, 54, 9, {{{33, 39}}}, 55);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Good: This adds another pointer parameter to the function of id 6.
+  TransformationAddParameter transformation_good_4(6, 56, 30, {{{33, 31}}}, 57);
+  ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Output %26
+         %61 = OpVariable %60 Output
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+         %10 = OpTypeFunction %8 %9 %30
+         %53 = OpTypeFunction %2 %9
+         %57 = OpTypeFunction %2 %9 %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6 %39 %31
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36 %31
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40 %31
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14 %34
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %57
+         %54 = OpFunctionParameter %9
+         %56 = OpFunctionParameter %30
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %50 = OpFunctionParameter %30
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %53
+         %52 = OpFunctionParameter %9
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationAddParameterTest, PointerPrivateWorkgroupTest) {
+  // This types handles case of adding a new parameter of a pointer type with
+  // storage class Private or Workgroup.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %8 %9
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Workgroup %26
+         %61 = OpVariable %60 Workgroup
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               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);
+  // Good: Global variable of id 28 (storage class Private) is defined in the
+  // caller (main).
+  TransformationAddParameter transformation_good_1(12, 70, 27,
+                                                   {{{38, 28}, {42, 28}}}, 71);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Good: Global variable of id 61 is (storage class Workgroup) is defined in
+  // the caller (main).
+  TransformationAddParameter transformation_good_2(12, 72, 27,
+                                                   {{{38, 28}, {42, 28}}}, 73);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_2, context.get(),
+                        &transformation_context);
+
+  // Good: Global variable of id 28 (storage class Private) is defined in the
+  // caller (main).
+  TransformationAddParameter transformation_good_3(6, 74, 27, {{{33, 28}}}, 75);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Good: Global variable of id 61 is (storage class Workgroup) is defined in
+  // the caller (main).
+  TransformationAddParameter transformation_good_4(6, 76, 60, {{{33, 61}}}, 77);
+  ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_4, context.get(),
+                        &transformation_context);
+
+  // Good: Global variable of id 28 (storage class Private) is defined in the
+  // caller (main).
+  TransformationAddParameter transformation_good_5(14, 78, 27, {{{43, 28}}},
+                                                   79);
+  ASSERT_TRUE(transformation_good_5.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Good: Global variable of id 61 is (storage class Workgroup) is defined in
+  // the caller (main).
+  TransformationAddParameter transformation_good_6(14, 80, 60, {{{43, 61}}},
+                                                   81);
+  ASSERT_TRUE(transformation_good_6.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Workgroup %26
+         %61 = OpVariable %60 Workgroup
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+         %10 = OpTypeFunction %8 %9 %27 %27
+         %75 = OpTypeFunction %2 %27 %60
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6 %28 %61
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36 %28 %28
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40 %28 %28
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14 %28 %61
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %75
+         %74 = OpFunctionParameter %27
+         %76 = OpFunctionParameter %60
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %70 = OpFunctionParameter %27
+         %72 = OpFunctionParameter %27
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %75
+         %78 = OpFunctionParameter %27
+         %80 = OpFunctionParameter %60
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationAddParameterTest, PointerMoreEntriesInMapTest) {
+  // This types handles case where call_parameter_id has an entry for at least
+  // every caller (there are more entries than it is necessary).
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %19 "i1"
+               OpName %21 "i2"
+               OpName %22 "i3"
+               OpName %24 "i4"
+               OpName %25 "param"
+               OpName %28 "i5"
+               OpName %29 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %15 = OpConstant %6 2
+         %20 = OpConstant %6 1
+         %23 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %7 Function
+         %28 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %19 %20
+               OpStore %21 %15
+               OpStore %22 %23
+         %26 = OpLoad %6 %19
+               OpStore %25 %26
+         %27 = OpFunctionCall %6 %10 %25
+               OpStore %24 %27
+         %30 = OpLoad %6 %21
+               OpStore %29 %30
+         %31 = OpFunctionCall %6 %10 %29
+               OpStore %28 %31
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpReturnValue %16
+               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);
+  // Good: Local variable of id 21 is defined in every caller (id 27 and id 31).
+  TransformationAddParameter transformation_good_1(
+      10, 70, 7, {{{27, 21}, {31, 21}, {30, 21}}}, 71);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Good: Local variable of id 28 is defined in every caller (id 27 and id 31).
+  TransformationAddParameter transformation_good_2(
+      10, 72, 7, {{{27, 28}, {31, 28}, {14, 21}, {16, 14}}}, 73);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+              OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %19 "i1"
+               OpName %21 "i2"
+               OpName %22 "i3"
+               OpName %24 "i4"
+               OpName %25 "param"
+               OpName %28 "i5"
+               OpName %29 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %15 = OpConstant %6 2
+         %20 = OpConstant %6 1
+         %23 = OpConstant %6 3
+          %8 = OpTypeFunction %6 %7 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %7 Function
+         %28 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %19 %20
+               OpStore %21 %15
+               OpStore %22 %23
+         %26 = OpLoad %6 %19
+               OpStore %25 %26
+         %27 = OpFunctionCall %6 %10 %25 %21 %28
+               OpStore %24 %27
+         %30 = OpLoad %6 %21
+               OpStore %29 %30
+         %31 = OpFunctionCall %6 %10 %29 %21 %28
+               OpStore %28 %31
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %70 = OpFunctionParameter %7
+         %72 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpReturnValue %16
+               OpFunctionEnd
+    )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationAddParameterTest, PointeeValueIsIrrelevantTest) {
+  // This test checks if the transformation has correctly applied the
+  // PointeeValueIsIrrelevant fact for new pointer parameters.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %20 "b"
+               OpName %22 "i1"
+               OpName %24 "i2"
+               OpName %25 "i3"
+               OpName %26 "param"
+               OpName %29 "i4"
+               OpName %30 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpTypePointer Workgroup %6
+         %51 = OpVariable %50 Workgroup
+          %8 = OpTypeFunction %6 %7
+         %15 = OpConstant %6 2
+         %19 = OpTypePointer Private %6
+         %20 = OpVariable %19 Private
+         %21 = OpConstant %6 0
+         %23 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %22 = OpVariable %7 Function
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %7 Function
+         %26 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+         %30 = OpVariable %7 Function
+               OpStore %20 %21
+               OpStore %22 %23
+               OpStore %24 %15
+         %27 = OpLoad %6 %22
+               OpStore %26 %27
+         %28 = OpFunctionCall %6 %10 %26
+               OpStore %25 %28
+         %31 = OpLoad %6 %24
+               OpStore %30 %31
+         %32 = OpFunctionCall %6 %10 %30
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpReturnValue %16
+               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);
+  TransformationAddParameter transformation_good_1(10, 70, 7,
+                                                   {{{28, 22}, {32, 22}}}, 71);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Check if the fact PointeeValueIsIrrelevant is set for the new parameter
+  // (storage class Function).
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(70));
+
+  TransformationAddParameter transformation_good_2(10, 72, 19,
+                                                   {{{28, 20}, {32, 20}}}, 73);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Check if the fact PointeeValueIsIrrelevant is set for the new parameter
+  // (storage class Private).
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(72));
+
+  TransformationAddParameter transformation_good_3(10, 74, 50,
+                                                   {{{28, 51}, {32, 51}}}, 75);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Check if the fact PointeeValueIsIrrelevant is set for the new parameter
+  // (storage class Workgroup).
+  ASSERT_TRUE(
+      transformation_context.GetFactManager()->PointeeValueIsIrrelevant(74));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_add_relaxed_decoration_test.cpp b/test/fuzz/transformation_add_relaxed_decoration_test.cpp
index 6e163ad..c440882 100644
--- a/test/fuzz/transformation_add_relaxed_decoration_test.cpp
+++ b/test/fuzz/transformation_add_relaxed_decoration_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_relaxed_decoration.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -64,18 +67,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  fact_manager.AddFactBlockIsDead(100);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(100);
 
   // Invalid: 200 is not an id.
   ASSERT_FALSE(TransformationAddRelaxedDecoration(200).IsApplicable(
       context.get(), transformation_context));
+  // Invalid: 1 is not in a block.
+  ASSERT_FALSE(TransformationAddRelaxedDecoration(1).IsApplicable(
+      context.get(), transformation_context));
   // Invalid: 27 is not in a dead block.
   ASSERT_FALSE(TransformationAddRelaxedDecoration(27).IsApplicable(
       context.get(), transformation_context));
@@ -88,8 +92,10 @@
     TransformationAddRelaxedDecoration transformation(result_id);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_add_synonym_test.cpp b/test/fuzz/transformation_add_synonym_test.cpp
index 1adc948..3803fa3 100644
--- a/test/fuzz/transformation_add_synonym_test.cpp
+++ b/test/fuzz/transformation_add_synonym_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_add_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"
 
@@ -68,14 +70,12 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  fact_manager.AddFactIdIsIrrelevant(24);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(24);
 
   auto insert_before = MakeInstructionDescriptor(22, SpvOpReturn, 0);
 
@@ -206,13 +206,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(5, SpvOpReturn, 0);
 
   uint32_t fresh_id = 50;
@@ -244,9 +242,10 @@
                                               insert_before);
       ASSERT_TRUE(
           transformation.IsApplicable(context.get(), transformation_context));
-      transformation.Apply(context.get(), &transformation_context);
-      ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(result_id, {}),
-                                            MakeDataDescriptor(fresh_id, {})));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+      ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+          MakeDataDescriptor(result_id, {}), MakeDataDescriptor(fresh_id, {})));
       ++fresh_id;
     }
   }
@@ -343,13 +342,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(5, SpvOpReturn, 0);
 
   uint32_t fresh_id = 50;
@@ -372,9 +369,10 @@
                                               insert_before);
       ASSERT_TRUE(
           transformation.IsApplicable(context.get(), transformation_context));
-      transformation.Apply(context.get(), &transformation_context);
-      ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(result_id, {}),
-                                            MakeDataDescriptor(fresh_id, {})));
+      ApplyAndCheckFreshIds(transformation, context.get(),
+                            &transformation_context);
+      ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+          MakeDataDescriptor(result_id, {}), MakeDataDescriptor(fresh_id, {})));
       ++fresh_id;
     }
   }
@@ -437,13 +435,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(5, SpvOpReturn, 0);
   const auto synonym_type = protobufs::TransformationAddSynonym::LOGICAL_AND;
 
@@ -477,13 +473,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(5, SpvOpReturn, 0);
   const auto synonym_type = protobufs::TransformationAddSynonym::LOGICAL_OR;
 
@@ -537,13 +531,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(5, SpvOpReturn, 0);
   const auto synonym_type = protobufs::TransformationAddSynonym::COPY_OBJECT;
 
@@ -556,9 +548,10 @@
                                             insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(result_id, {}),
-                                          MakeDataDescriptor(fresh_id, {})));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(result_id, {}), MakeDataDescriptor(fresh_id, {})));
     ++fresh_id;
   }
 
@@ -634,13 +627,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_EQ(0, transformation_context.GetFactManager()
                    ->GetIdsForWhichSynonymsAreKnown()
                    .size());
@@ -650,7 +641,7 @@
         7, protobufs::TransformationAddSynonym::COPY_OBJECT, 100,
         MakeInstructionDescriptor(5, SpvOpReturn, 0));
     ASSERT_TRUE(copy_true.IsApplicable(context.get(), transformation_context));
-    copy_true.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(copy_true, context.get(), &transformation_context);
 
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
         transformation_context.GetFactManager()
@@ -671,7 +662,7 @@
         8, protobufs::TransformationAddSynonym::COPY_OBJECT, 101,
         MakeInstructionDescriptor(100, SpvOpReturn, 0));
     ASSERT_TRUE(copy_false.IsApplicable(context.get(), transformation_context));
-    copy_false.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(copy_false, context.get(), &transformation_context);
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
         transformation_context.GetFactManager()
             ->GetIdsForWhichSynonymsAreKnown();
@@ -692,7 +683,8 @@
         MakeInstructionDescriptor(5, SpvOpReturn, 0));
     ASSERT_TRUE(
         copy_false_again.IsApplicable(context.get(), transformation_context));
-    copy_false_again.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(copy_false_again, context.get(),
+                          &transformation_context);
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
         transformation_context.GetFactManager()
             ->GetIdsForWhichSynonymsAreKnown();
@@ -714,7 +706,8 @@
         MakeInstructionDescriptor(102, SpvOpReturn, 0));
     ASSERT_TRUE(
         copy_true_again.IsApplicable(context.get(), transformation_context));
-    copy_true_again.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(copy_true_again, context.get(),
+                          &transformation_context);
     std::vector<uint32_t> ids_for_which_synonyms_are_known =
         transformation_context.GetFactManager()
             ->GetIdsForWhichSynonymsAreKnown();
@@ -942,13 +935,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Inapplicable because %18 is decorated.
   ASSERT_FALSE(TransformationAddSynonym(
                    18, protobufs::TransformationAddSynonym::COPY_OBJECT, 200,
@@ -1137,13 +1128,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   std::vector<TransformationAddSynonym> transformations = {
       TransformationAddSynonym(
           19, protobufs::TransformationAddSynonym::COPY_OBJECT, 100,
@@ -1170,10 +1159,12 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1247,13 +1238,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Illegal to copy null.
   ASSERT_FALSE(TransformationAddSynonym(
                    8, protobufs::TransformationAddSynonym::COPY_OBJECT, 100,
@@ -1293,13 +1282,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(8);
 
   TransformationAddSynonym transformation1(
@@ -1314,13 +1301,16 @@
 
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
 
   ASSERT_TRUE(
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(8));
@@ -1334,7 +1324,7 @@
       transformation_context.GetFactManager()->PointeeValueIsIrrelevant(101));
 }
 
-TEST(TransformationAddSynonym, DoNotCopyOpSampledImage) {
+TEST(TransformationAddSynonymTest, DoNotCopyOpSampledImage) {
   // This checks that we do not try to copy the result id of an OpSampledImage
   // instruction.
   std::string shader = R"(
@@ -1380,11 +1370,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_FALSE(
       TransformationAddSynonym(
           216, protobufs::TransformationAddSynonym::COPY_OBJECT, 500,
@@ -1392,6 +1380,93 @@
           .IsApplicable(context.get(), transformation_context));
 }
 
+TEST(TransformationAddSynonymTest, DoNotCopyVoidRunctionResult) {
+  // This checks that we do not try to copy the result of a void function.
+  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 %6 "foo("
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = 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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_FALSE(TransformationAddSynonym(
+                   8, protobufs::TransformationAddSynonym::COPY_OBJECT, 500,
+                   MakeInstructionDescriptor(8, SpvOpReturn, 0))
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddSynonymTest, HandlesDeadBlocks) {
+  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
+         %11 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %11 Function
+               OpSelectionMerge %10 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+               OpBranch %10
+         %10 = 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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(9);
+
+  auto insert_before = MakeInstructionDescriptor(9, SpvOpBranch, 0);
+
+  ASSERT_FALSE(TransformationAddSynonym(
+                   7, protobufs::TransformationAddSynonym::COPY_OBJECT, 100,
+                   insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+
+  ASSERT_FALSE(TransformationAddSynonym(
+                   12, protobufs::TransformationAddSynonym::COPY_OBJECT, 100,
+                   insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_add_type_array_test.cpp b/test/fuzz/transformation_add_type_array_test.cpp
index 4392f99..ab4ed9a 100644
--- a/test/fuzz/transformation_add_type_array_test.cpp
+++ b/test/fuzz/transformation_add_type_array_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_array.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -51,13 +54,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(TransformationAddTypeArray(4, 10, 16).IsApplicable(
       context.get(), transformation_context));
@@ -99,9 +100,11 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_type_boolean_test.cpp b/test/fuzz/transformation_add_type_boolean_test.cpp
index 60eabd9..88d9f5b 100644
--- a/test/fuzz/transformation_add_type_boolean_test.cpp
+++ b/test/fuzz/transformation_add_type_boolean_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_boolean.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -39,13 +42,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Not applicable because id 1 is already in use.
   ASSERT_FALSE(TransformationAddTypeBoolean(1).IsApplicable(
       context.get(), transformation_context));
@@ -53,8 +54,9 @@
   auto add_type_bool = TransformationAddTypeBoolean(100);
   ASSERT_TRUE(
       add_type_bool.IsApplicable(context.get(), transformation_context));
-  add_type_bool.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(add_type_bool, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Not applicable as we already have this type now.
   ASSERT_FALSE(TransformationAddTypeBoolean(101).IsApplicable(
diff --git a/test/fuzz/transformation_add_type_float_test.cpp b/test/fuzz/transformation_add_type_float_test.cpp
index 7d17266..235d61b 100644
--- a/test/fuzz/transformation_add_type_float_test.cpp
+++ b/test/fuzz/transformation_add_type_float_test.cpp
@@ -13,70 +13,131 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_float.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
 namespace fuzz {
 namespace {
 
-TEST(TransformationAddTypeFloatTest, BasicTest) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
+TEST(TransformationAddTypeFloatTest, IsApplicable) {
+  std::string reference_shader = R"(
+         OpCapability Shader
+         OpCapability Float16
+    %1 = OpExtInstImport "GLSL.std.450"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint Vertex %5 "main"
+
+; Types
+    %2 = OpTypeFloat 16
+    %3 = OpTypeVoid
+    %4 = OpTypeFunction %3
+
+; main function
+    %5 = OpFunction %3 None %4
+    %6 = OpLabel
+         OpReturn
+         OpFunctionEnd
   )";
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Tests non-fresh id.
+  auto transformation = TransformationAddTypeFloat(1, 32);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // Not applicable because id 1 is already in use.
-  ASSERT_FALSE(TransformationAddTypeFloat(1, 32).IsApplicable(
-      context.get(), transformation_context));
+  // Tests missing Float64 capability.
+  transformation = TransformationAddTypeFloat(7, 64);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  auto add_type_float_32 = TransformationAddTypeFloat(100, 32);
+  // Tests existing 16-bit float type.
+  transformation = TransformationAddTypeFloat(7, 16);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests adding 32-bit float type.
+  transformation = TransformationAddTypeFloat(7, 32);
   ASSERT_TRUE(
-      add_type_float_32.IsApplicable(context.get(), transformation_context));
-  add_type_float_32.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+      transformation.IsApplicable(context.get(), transformation_context));
+}
 
-  // Not applicable as we already have this type now.
-  ASSERT_FALSE(TransformationAddTypeFloat(101, 32).IsApplicable(
-      context.get(), transformation_context));
+TEST(TransformationAddTypeFloatTest, Apply) {
+  std::string reference_shader = R"(
+         OpCapability Shader
+         OpCapability Float16
+         OpCapability Float64
+    %1 = OpExtInstImport "GLSL.std.450"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint Vertex %4 "main"
 
-  std::string after_transformation = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-        %100 = OpTypeFloat 32
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
+; Types
+    %2 = OpTypeVoid
+    %3 = OpTypeFunction %2
+
+; main function
+    %4 = OpFunction %2 None %3
+    %5 = OpLabel
+         OpReturn
+         OpFunctionEnd
   )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  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);
+  // Adds 16-bit float type.
+  auto transformation = TransformationAddTypeFloat(6, 16);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds 32-bit float type.
+  transformation = TransformationAddTypeFloat(7, 32);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds 64-bit float type.
+  transformation = TransformationAddTypeFloat(8, 64);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+         OpCapability Shader
+         OpCapability Float16
+         OpCapability Float64
+    %1 = OpExtInstImport "GLSL.std.450"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint Vertex %4 "main"
+
+; Types
+    %2 = OpTypeVoid
+    %3 = OpTypeFunction %2
+    %6 = OpTypeFloat 16
+    %7 = OpTypeFloat 32
+    %8 = OpTypeFloat 64
+
+; main function
+    %4 = OpFunction %2 None %3
+    %5 = OpLabel
+         OpReturn
+         OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_type_function_test.cpp b/test/fuzz/transformation_add_type_function_test.cpp
index 1557bb8..5d9ea47 100644
--- a/test/fuzz/transformation_add_type_function_test.cpp
+++ b/test/fuzz/transformation_add_type_function_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_function.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -56,13 +59,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(TransformationAddTypeFunction(4, 12, {12, 16, 14})
                    .IsApplicable(context.get(), transformation_context));
@@ -91,9 +92,11 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_type_int_test.cpp b/test/fuzz/transformation_add_type_int_test.cpp
index 63b17c2..ee4e799 100644
--- a/test/fuzz/transformation_add_type_int_test.cpp
+++ b/test/fuzz/transformation_add_type_int_test.cpp
@@ -13,83 +13,173 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_int.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
 namespace fuzz {
 namespace {
 
-TEST(TransformationAddTypeIntTest, BasicTest) {
-  std::string shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
+TEST(TransformationAddTypeIntTest, IsApplicable) {
+  std::string reference_shader = R"(
+         OpCapability Shader
+         OpCapability Int8
+    %1 = OpExtInstImport "GLSL.std.450"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint Vertex %5 "main"
+
+; Types
+    %2 = OpTypeInt 8 1
+    %3 = OpTypeVoid
+    %4 = OpTypeFunction %3
+
+; main function
+    %5 = OpFunction %3 None %4
+    %6 = OpLabel
+         OpReturn
+         OpFunctionEnd
   )";
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Tests non-fresh id.
+  auto transformation = TransformationAddTypeInt(1, 32, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // Not applicable because id 1 is already in use.
-  ASSERT_FALSE(TransformationAddTypeInt(1, 32, false)
-                   .IsApplicable(context.get(), transformation_context));
+  // Tests missing Int16 capability.
+  transformation = TransformationAddTypeInt(7, 16, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  auto add_type_signed_int_32 = TransformationAddTypeInt(100, 32, true);
-  auto add_type_unsigned_int_32 = TransformationAddTypeInt(101, 32, false);
-  auto add_type_signed_int_32_again = TransformationAddTypeInt(102, 32, true);
-  auto add_type_unsigned_int_32_again =
-      TransformationAddTypeInt(103, 32, false);
+  // Tests missing Int64 capability.
+  transformation = TransformationAddTypeInt(7, 64, false);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(add_type_signed_int_32.IsApplicable(context.get(),
-                                                  transformation_context));
-  add_type_signed_int_32.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  // Tests existing signed 8-bit integer type.
+  transformation = TransformationAddTypeInt(7, 8, true);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(add_type_unsigned_int_32.IsApplicable(context.get(),
-                                                    transformation_context));
-  add_type_unsigned_int_32.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  // Tests adding unsigned 8-bit integer type.
+  transformation = TransformationAddTypeInt(7, 8, false);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
 
-  // Not applicable as we already have these types now.
-  ASSERT_FALSE(add_type_signed_int_32_again.IsApplicable(
-      context.get(), transformation_context));
-  ASSERT_FALSE(add_type_unsigned_int_32_again.IsApplicable(
-      context.get(), transformation_context));
+  // Tests adding unsigned 32-bit integer type.
+  transformation = TransformationAddTypeInt(7, 32, false);
+  ASSERT_TRUE(
+      transformation.IsApplicable(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"
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-        %100 = OpTypeInt 32 1
-        %101 = OpTypeInt 32 0
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpReturn
-               OpFunctionEnd
+  // Tests adding signed 32-bit integer type.
+  transformation = TransformationAddTypeInt(7, 32, true);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddTypeIntTest, Apply) {
+  std::string reference_shader = R"(
+         OpCapability Shader
+         OpCapability Int8
+         OpCapability Int16
+         OpCapability Int64
+    %1 = OpExtInstImport "GLSL.std.450"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint Vertex %4 "main"
+
+; Types
+    %2 = OpTypeVoid
+    %3 = OpTypeFunction %2
+
+; main function
+    %4 = OpFunction %2 None %3
+    %5 = OpLabel
+         OpReturn
+         OpFunctionEnd
   )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  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);
+  // Adds signed 8-bit integer type.
+  auto transformation = TransformationAddTypeInt(6, 8, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds signed 16-bit integer type.
+  transformation = TransformationAddTypeInt(7, 16, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds signed 32-bit integer type.
+  transformation = TransformationAddTypeInt(8, 32, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds signed 64-bit integer type.
+  transformation = TransformationAddTypeInt(9, 64, true);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds unsigned 8-bit integer type.
+  transformation = TransformationAddTypeInt(10, 8, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds unsigned 16-bit integer type.
+  transformation = TransformationAddTypeInt(11, 16, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds unsigned 32-bit integer type.
+  transformation = TransformationAddTypeInt(12, 32, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Adds unsigned 64-bit integer type.
+  transformation = TransformationAddTypeInt(13, 64, false);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+         OpCapability Shader
+         OpCapability Int8
+         OpCapability Int16
+         OpCapability Int64
+    %1 = OpExtInstImport "GLSL.std.450"
+         OpMemoryModel Logical GLSL450
+         OpEntryPoint Vertex %4 "main"
+
+; Types
+    %2 = OpTypeVoid
+    %3 = OpTypeFunction %2
+    %6 = OpTypeInt 8 1
+    %7 = OpTypeInt 16 1
+    %8 = OpTypeInt 32 1
+    %9 = OpTypeInt 64 1
+   %10 = OpTypeInt 8 0
+   %11 = OpTypeInt 16 0
+   %12 = OpTypeInt 32 0
+   %13 = OpTypeInt 64 0
+
+; main function
+    %4 = OpFunction %2 None %3
+    %5 = OpLabel
+         OpReturn
+         OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_add_type_matrix_test.cpp b/test/fuzz/transformation_add_type_matrix_test.cpp
index e925012..926e983 100644
--- a/test/fuzz/transformation_add_type_matrix_test.cpp
+++ b/test/fuzz/transformation_add_type_matrix_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_matrix.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -44,13 +47,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(TransformationAddTypeMatrix(4, 9, 2).IsApplicable(
       context.get(), transformation_context));
@@ -93,9 +94,11 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_add_type_pointer_test.cpp b/test/fuzz/transformation_add_type_pointer_test.cpp
index 35303e4..985e904 100644
--- a/test/fuzz/transformation_add_type_pointer_test.cpp
+++ b/test/fuzz/transformation_add_type_pointer_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_pointer.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -94,13 +97,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto bad_type_id_does_not_exist =
       TransformationAddTypePointer(100, SpvStorageClassFunction, 101);
   auto bad_type_id_is_not_type =
@@ -141,8 +142,10 @@
         good_new_private_pointer_to_uniform_pointer_to_vec2}) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_add_type_struct_test.cpp b/test/fuzz/transformation_add_type_struct_test.cpp
index 06f78cd..b57bab2 100644
--- a/test/fuzz/transformation_add_type_struct_test.cpp
+++ b/test/fuzz/transformation_add_type_struct_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_struct.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -44,13 +47,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(TransformationAddTypeStruct(4, {}).IsApplicable(
       context.get(), transformation_context));
@@ -78,9 +79,11 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -109,6 +112,47 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationAddTypeStructTest, HandlesBuiltInMembers) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %2 "main"
+               OpMemberDecorate %4 0 BuiltIn Position
+               OpMemberDecorate %4 1 BuiltIn PointSize
+               OpMemberDecorate %4 2 BuiltIn ClipDistance
+          %6 = OpTypeFloat 32
+          %5 = OpTypeVector %6 4
+          %9 = OpTypeInt 32 1
+          %8 = OpConstant %9 1
+          %7 = OpTypeArray %6 %8
+          %4 = OpTypeStruct %5 %6 %7
+         %27 = OpTypeVoid
+         %28 = OpTypeFunction %27
+          %2 = OpFunction %27 None %28
+         %29 = OpLabel
+               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);
+  // 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.
+  //
+  // OpTypeStruct with id %4 has BuiltIn members.
+  ASSERT_FALSE(TransformationAddTypeStruct(50, {6, 5, 4, 6, 7})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_add_type_vector_test.cpp b/test/fuzz/transformation_add_type_vector_test.cpp
index f1252a3..a49ba6e 100644
--- a/test/fuzz/transformation_add_type_vector_test.cpp
+++ b/test/fuzz/transformation_add_type_vector_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_add_type_vector.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -42,13 +45,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Id already in use
   ASSERT_FALSE(TransformationAddTypeVector(4, 6, 2).IsApplicable(
       context.get(), transformation_context));
@@ -72,9 +73,11 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_adjust_branch_weights_test.cpp b/test/fuzz/transformation_adjust_branch_weights_test.cpp
index 7f8ba31..1bf2c59 100644
--- a/test/fuzz/transformation_adjust_branch_weights_test.cpp
+++ b/test/fuzz/transformation_adjust_branch_weights_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_adjust_branch_weights.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -98,13 +101,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests OpBranchConditional instruction with weigths.
   auto instruction_descriptor =
       MakeInstructionDescriptor(33, SpvOpBranchConditional, 0);
@@ -248,24 +249,22 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor =
       MakeInstructionDescriptor(33, SpvOpBranchConditional, 0);
   auto transformation =
       TransformationAdjustBranchWeights(instruction_descriptor, {5, 6});
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(21, SpvOpBranchConditional, 0);
   transformation =
       TransformationAdjustBranchWeights(instruction_descriptor, {7, 8});
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_composite_construct_test.cpp b/test/fuzz/transformation_composite_construct_test.cpp
index df6f382..d2a18b0 100644
--- a/test/fuzz/transformation_composite_construct_test.cpp
+++ b/test/fuzz/transformation_composite_construct_test.cpp
@@ -14,7 +14,9 @@
 
 #include "source/fuzz/transformation_composite_construct.h"
 
+#include "gtest/gtest.h"
 #include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -127,13 +129,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Make a vec2[3]
   TransformationCompositeConstruct make_vec2_array_length_3(
       37, {41, 45, 27}, MakeInstructionDescriptor(46, SpvOpAccessChain, 0),
@@ -146,8 +146,10 @@
                                                     transformation_context));
   ASSERT_FALSE(make_vec2_array_length_3_bad.IsApplicable(
       context.get(), transformation_context));
-  make_vec2_array_length_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_vec2_array_length_3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(41, {}), MakeDataDescriptor(200, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -165,8 +167,10 @@
                                                      transformation_context));
   ASSERT_FALSE(make_float_array_length_2_bad.IsApplicable(
       context.get(), transformation_context));
-  make_float_array_length_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_float_array_length_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(24, {}), MakeDataDescriptor(201, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -184,8 +188,10 @@
                                                     transformation_context));
   ASSERT_FALSE(make_bool_array_length_3_bad.IsApplicable(
       context.get(), transformation_context));
-  make_bool_array_length_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_bool_array_length_3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(33, {}), MakeDataDescriptor(202, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -203,8 +209,10 @@
                                                        transformation_context));
   ASSERT_FALSE(make_uvec3_array_length_2_2_bad.IsApplicable(
       context.get(), transformation_context));
-  make_uvec3_array_length_2_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_uvec3_array_length_2_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(69, {}), MakeDataDescriptor(203, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -393,13 +401,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // make a mat3x4
   TransformationCompositeConstruct make_mat34(
       32, {25, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200);
@@ -409,8 +415,9 @@
   ASSERT_TRUE(make_mat34.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_mat34_bad.IsApplicable(context.get(), transformation_context));
-  make_mat34.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_mat34, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -427,8 +434,9 @@
   ASSERT_TRUE(make_mat43.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_mat43_bad.IsApplicable(context.get(), transformation_context));
-  make_mat43.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_mat43, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(11, {}), MakeDataDescriptor(201, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -606,13 +614,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // make an Inner
   TransformationCompositeConstruct make_inner(
       9, {25, 19}, MakeInstructionDescriptor(57, SpvOpAccessChain, 0), 200);
@@ -622,8 +628,9 @@
   ASSERT_TRUE(make_inner.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_inner_bad.IsApplicable(context.get(), transformation_context));
-  make_inner.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_inner, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -640,8 +647,9 @@
   ASSERT_TRUE(make_outer.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_outer_bad.IsApplicable(context.get(), transformation_context));
-  make_outer.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_outer, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(46, {}), MakeDataDescriptor(201, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -930,13 +938,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationCompositeConstruct make_vec2(
       7, {17, 11}, MakeInstructionDescriptor(100, SpvOpStore, 0), 200);
   // Bad: not enough data for a vec2
@@ -945,8 +951,9 @@
   ASSERT_TRUE(make_vec2.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_vec2_bad.IsApplicable(context.get(), transformation_context));
-  make_vec2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_vec2, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(17, {}), MakeDataDescriptor(200, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -962,8 +969,9 @@
   ASSERT_TRUE(make_vec3.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_vec3_bad.IsApplicable(context.get(), transformation_context));
-  make_vec3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_vec3, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(12, {0}), MakeDataDescriptor(201, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -981,8 +989,9 @@
   ASSERT_TRUE(make_vec4.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_vec4_bad.IsApplicable(context.get(), transformation_context));
-  make_vec4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_vec4, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(32, {}), MakeDataDescriptor(202, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1000,8 +1009,9 @@
   ASSERT_TRUE(make_ivec2.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_ivec2_bad.IsApplicable(context.get(), transformation_context));
-  make_ivec2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_ivec2, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(126, {}), MakeDataDescriptor(203, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1017,8 +1027,9 @@
   ASSERT_TRUE(make_ivec3.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_ivec3_bad.IsApplicable(context.get(), transformation_context));
-  make_ivec3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_ivec3, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(56, {}), MakeDataDescriptor(204, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1036,8 +1047,9 @@
   ASSERT_TRUE(make_ivec4.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_ivec4_bad.IsApplicable(context.get(), transformation_context));
-  make_ivec4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_ivec4, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(56, {}), MakeDataDescriptor(205, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1054,8 +1066,9 @@
   ASSERT_TRUE(make_uvec2.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_uvec2_bad.IsApplicable(context.get(), transformation_context));
-  make_uvec2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_uvec2, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(18, {}), MakeDataDescriptor(206, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1069,8 +1082,9 @@
   ASSERT_TRUE(make_uvec3.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_uvec3_bad.IsApplicable(context.get(), transformation_context));
-  make_uvec3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_uvec3, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(14, {}), MakeDataDescriptor(207, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1088,8 +1102,9 @@
   ASSERT_TRUE(make_uvec4.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_uvec4_bad.IsApplicable(context.get(), transformation_context));
-  make_uvec4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_uvec4, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(14, {}), MakeDataDescriptor(208, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1117,8 +1132,9 @@
   ASSERT_TRUE(make_bvec2.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_bvec2_bad.IsApplicable(context.get(), transformation_context));
-  make_bvec2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_bvec2, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(111, {}), MakeDataDescriptor(209, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1132,8 +1148,9 @@
   ASSERT_TRUE(make_bvec3.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_bvec3_bad.IsApplicable(context.get(), transformation_context));
-  make_bvec3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_bvec3, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(108, {0}), MakeDataDescriptor(210, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1149,8 +1166,9 @@
   ASSERT_TRUE(make_bvec4.IsApplicable(context.get(), transformation_context));
   ASSERT_FALSE(
       make_bvec4_bad.IsApplicable(context.get(), transformation_context));
-  make_bvec4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(make_bvec4, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(108, {0}), MakeDataDescriptor(211, {0})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -1424,23 +1442,22 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationCompositeConstruct transformation(
       32, {25, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}),
-                                        MakeDataDescriptor(200, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}),
-                                        MakeDataDescriptor(200, {1})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(28, {}), MakeDataDescriptor(200, {1})));
 }
 
 TEST(TransformationCompositeConstructTest, DontAddSynonymsForIrrelevantIds) {
@@ -1508,25 +1525,122 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  fact_manager.AddFactIdIsIrrelevant(25);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(25);
 
   TransformationCompositeConstruct transformation(
       32, {25, 28, 31}, MakeInstructionDescriptor(31, SpvOpReturn, 0), 200);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(25, {}),
-                                         MakeDataDescriptor(200, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(28, {}),
-                                        MakeDataDescriptor(200, {1})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(200, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(28, {}), MakeDataDescriptor(200, {1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(31, {}), MakeDataDescriptor(200, {2})));
+}
+
+TEST(TransformationCompositeConstructTest, DontAddSynonymsInDeadBlock) {
+  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 0
+         %11 = OpConstant %6 1
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeBool
+         %14 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+               OpStore %9 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %16
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  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(15);
+
+  TransformationCompositeConstruct transformation(
+      7, {10, 11}, MakeInstructionDescriptor(15, SpvOpBranch, 0), 100);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {0}), MakeDataDescriptor(10, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {1}), MakeDataDescriptor(11, {})));
+}
+
+TEST(TransformationCompositeConstructTest, OneIrrelevantComponent) {
+  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 = OpTypeStruct %6 %6 %6
+          %8 = OpConstant %6 42
+          %9 = OpConstant %6 50
+         %10 = OpConstant %6 51
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  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()->AddFactIdIsIrrelevant(8);
+
+  TransformationCompositeConstruct transformation(
+      7, {8, 9, 10}, MakeInstructionDescriptor(5, SpvOpReturn, 0), 100);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {0}), MakeDataDescriptor(8, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {1}), MakeDataDescriptor(9, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {2}), MakeDataDescriptor(10, {})));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_composite_extract_test.cpp b/test/fuzz/transformation_composite_extract_test.cpp
index 5b1a0f1..383a4db 100644
--- a/test/fuzz/transformation_composite_extract_test.cpp
+++ b/test/fuzz/transformation_composite_extract_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_composite_extract.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -94,22 +96,21 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Instruction does not exist.
   ASSERT_FALSE(TransformationCompositeExtract(
                    MakeInstructionDescriptor(36, SpvOpIAdd, 0), 200, 101, {0})
                    .IsApplicable(context.get(), transformation_context));
 
   // Id for composite is not a composite.
-  ASSERT_FALSE(TransformationCompositeExtract(
-                   MakeInstructionDescriptor(36, SpvOpIAdd, 0), 200, 27, {})
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationCompositeExtract(
+          MakeInstructionDescriptor(37, SpvOpAccessChain, 0), 200, 32, {})
+          .IsApplicable(context.get(), transformation_context));
 
   // Composite does not dominate instruction being inserted before.
   ASSERT_FALSE(
@@ -144,43 +145,55 @@
       MakeInstructionDescriptor(36, SpvOpConvertFToS, 0), 201, 100, {2});
   ASSERT_TRUE(
       transformation_1.IsApplicable(context.get(), transformation_context));
-  transformation_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   TransformationCompositeExtract transformation_2(
       MakeInstructionDescriptor(37, SpvOpAccessChain, 0), 202, 104, {0, 2});
   ASSERT_TRUE(
       transformation_2.IsApplicable(context.get(), transformation_context));
-  transformation_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   TransformationCompositeExtract transformation_3(
       MakeInstructionDescriptor(29, SpvOpAccessChain, 0), 203, 104, {0});
   ASSERT_TRUE(
       transformation_3.IsApplicable(context.get(), transformation_context));
-  transformation_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   TransformationCompositeExtract transformation_4(
       MakeInstructionDescriptor(24, SpvOpStore, 0), 204, 101, {0});
   ASSERT_TRUE(
       transformation_4.IsApplicable(context.get(), transformation_context));
-  transformation_4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   TransformationCompositeExtract transformation_5(
       MakeInstructionDescriptor(29, SpvOpBranch, 0), 205, 102, {2});
   ASSERT_TRUE(
       transformation_5.IsApplicable(context.get(), transformation_context));
-  transformation_5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   TransformationCompositeExtract transformation_6(
       MakeInstructionDescriptor(37, SpvOpReturn, 0), 206, 103, {1});
   ASSERT_TRUE(
       transformation_6.IsApplicable(context.get(), transformation_context));
-  transformation_6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(201, {}), MakeDataDescriptor(100, {2})));
@@ -349,13 +362,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Cannot insert before the OpVariables of a function.
   ASSERT_FALSE(
       TransformationCompositeExtract(
@@ -473,20 +484,18 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationCompositeExtract transformation(
       MakeInstructionDescriptor(36, SpvOpConvertFToS, 0), 201, 100, {2});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(201, {}),
-                                        MakeDataDescriptor(100, {2})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(201, {}), MakeDataDescriptor(100, {2})));
 }
 
 TEST(TransformationCompositeExtractTest, DontAddSynonymsForIrrelevantIds) {
@@ -562,21 +571,68 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  fact_manager.AddFactIdIsIrrelevant(100);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(100);
   TransformationCompositeExtract transformation(
       MakeInstructionDescriptor(36, SpvOpConvertFToS, 0), 201, 100, {2});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(201, {}),
-                                         MakeDataDescriptor(100, {2})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(201, {}), MakeDataDescriptor(100, {2})));
+}
+
+TEST(TransformationCompositeExtractTest, DontAddSynonymInDeadBlock) {
+  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 0
+         %11 = OpConstant %6 1
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeBool
+         %14 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+               OpStore %9 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %16
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               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);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(15);
+  TransformationCompositeExtract transformation(
+      MakeInstructionDescriptor(15, SpvOpBranch, 0), 100, 12, {0});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(12, {0})));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_composite_insert_test.cpp b/test/fuzz/transformation_composite_insert_test.cpp
new file mode 100644
index 0000000..3b6f34d
--- /dev/null
+++ b/test/fuzz/transformation_composite_insert_test.cpp
@@ -0,0 +1,920 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_composite_insert.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/data_descriptor.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(TransformationCompositeInsertTest, NotApplicableScenarios) {
+  // This test handles cases where IsApplicable() returns false.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpName %20 "l1"
+               OpName %24 "level_2"
+               OpMemberName %24 0 "c1"
+               OpMemberName %24 1 "c2"
+               OpName %26 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12
+         %19 = OpTypePointer Function %18
+         %24 = OpTypeStruct %18 %18
+         %25 = OpTypePointer Function %24
+         %30 = OpTypeBool
+         %31 = OpConstantTrue %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %26 = OpVariable %25 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpCompositeConstruct %18 %21 %22
+               OpStore %20 %23
+         %27 = OpLoad %18 %20
+         %28 = OpLoad %18 %20
+         %29 = OpCompositeConstruct %24 %27 %28
+               OpStore %26 %29
+               OpSelectionMerge %33 None
+               OpBranchConditional %31 %32 %33
+         %32 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+               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: |fresh_id| is not fresh.
+  auto transformation_bad_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 20, 29, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |composite_id| does not refer to a existing instruction.
+  auto transformation_bad_2 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 40, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |composite_id| does not refer to a composite value.
+  auto transformation_bad_3 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 9, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |object_id| does not refer to a defined instruction.
+  auto transformation_bad_4 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 40, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |object_id| cannot refer to a pointer.
+  auto transformation_bad_5 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 8, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_5.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |index| is not a correct index.
+  auto transformation_bad_6 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {2, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_6.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Type id of the object to be inserted and the type id of the
+  // component at |index| are not the same.
+  auto transformation_bad_7 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 29, 11, {1, 0});
+  ASSERT_FALSE(
+      transformation_bad_7.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |instruction_to_insert_before| does not refer to a defined
+  // instruction.
+  auto transformation_bad_8 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpIMul, 0), 50, 29, 11, {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_8.IsApplicable(context.get(), transformation_context));
+
+  // Bad: OpCompositeInsert cannot be inserted before OpBranchConditional with
+  // OpSelectionMerge above it.
+  auto transformation_bad_9 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpBranchConditional, 0), 50, 29, 11,
+      {1, 0, 0});
+  ASSERT_FALSE(
+      transformation_bad_9.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |composite_id| does not have a type_id.
+  auto transformation_bad_10 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(29, SpvOpStore, 0), 50, 1, 11, {1, 0, 0});
+  ASSERT_FALSE(transformation_bad_10.IsApplicable(context.get(),
+                                                  transformation_context));
+}
+
+TEST(TransformationCompositeInsertTest, EmptyCompositeScenarios) {
+  // This test handles cases where either the composite is empty or the
+  // composite contains an empty composite.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+          %2 = OpTypeVoid
+         %60 = OpTypeStruct
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %61 = OpConstantComposite %60
+         %62 = OpConstantComposite %60
+         %12 = OpTypeStruct %6 %6
+         %63 = OpTypeStruct %6 %60
+         %13 = OpTypePointer Function %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+         %64 = OpCompositeConstruct %63 %15 %61
+               OpStore %14 %17
+               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: The composite with |composite_id| cannot be empty.
+  auto transformation_bad_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 61, 62, {1});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Good: It is possible to insert into a composite an element which is an
+  // empty composite.
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(64, SpvOpStore, 0), 50, 64, 62, {1});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+          %2 = OpTypeVoid
+         %60 = OpTypeStruct
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %61 = OpConstantComposite %60
+         %62 = OpConstantComposite %60
+         %12 = OpTypeStruct %6 %6
+         %63 = OpTypeStruct %6 %60
+         %13 = OpTypePointer Function %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+         %64 = OpCompositeConstruct %63 %15 %61
+         %50 = OpCompositeInsert %63 %62 %64 1
+               OpStore %14 %17
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationCompositeInsertTest, IrrelevantCompositeNoSynonyms) {
+  // This test handles cases where either |composite| is irrelevant.
+  // The transformation shouldn't create any synonyms.
+  // The member composite has a different number of elements than the parent
+  // composite.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               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);
+  // Add fact that the composite is irrelevant.
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(30);
+
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // No synonyms that involve the original object - %30 - should have been
+  // added.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {0}), MakeDataDescriptor(50, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 1}), MakeDataDescriptor(50, {1, 1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 2}), MakeDataDescriptor(50, {1, 2})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 0, 1}), MakeDataDescriptor(50, {1, 0, 1})));
+  // We *should* have a synonym between %11 and the component of %50 into which
+  // it has been inserted.
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {1, 0, 0}), MakeDataDescriptor(11, {})));
+}
+
+TEST(TransformationCompositeInsertTest, IrrelevantObjectNoSynonyms) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               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);
+  // Add fact that the object is irrelevant.
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(11);
+
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Since %30 and %50 are not irrelevant, they should be synonymous at all
+  // indices unaffected by the insertion.
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {0}), MakeDataDescriptor(50, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 1}), MakeDataDescriptor(50, {1, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 2}), MakeDataDescriptor(50, {1, 2})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 0, 1}), MakeDataDescriptor(50, {1, 0, 1})));
+  // Since %11 is irrelevant it should not be synonymous with the component into
+  // which it has been inserted.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {1, 0, 0}), MakeDataDescriptor(11, {})));
+}
+
+TEST(TransformationCompositeInsertTest, ApplicableCreatedSynonyms) {
+  // This test handles cases where neither |composite| nor |object| is
+  // irrelevant. The transformation should create synonyms.
+  // The member composite has a different number of elements than the parent
+  // composite.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               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);
+  auto transformation_good_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(30, SpvOpStore, 0), 50, 30, 11, {1, 0, 0});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // These synonyms should have been added.
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {0}), MakeDataDescriptor(50, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 1}), MakeDataDescriptor(50, {1, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 2}), MakeDataDescriptor(50, {1, 2})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 0, 1}), MakeDataDescriptor(50, {1, 0, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {1, 0, 0}), MakeDataDescriptor(11, {})));
+
+  // These synonyms should not have been added.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1}), MakeDataDescriptor(50, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 0}), MakeDataDescriptor(50, {1, 0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1, 0, 0}), MakeDataDescriptor(50, {1, 0, 0})));
+
+  auto transformation_good_2 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(50, SpvOpStore, 0), 51, 50, 11, {0, 1, 1});
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // These synonyms should have been added.
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {1}), MakeDataDescriptor(51, {1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {0, 0}), MakeDataDescriptor(51, {0, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {0, 2}), MakeDataDescriptor(51, {0, 2})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {0, 1, 0}), MakeDataDescriptor(51, {0, 1, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(51, {0, 1, 1}), MakeDataDescriptor(11, {})));
+
+  // These synonyms should not have been added.
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {0}), MakeDataDescriptor(51, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {0, 1}), MakeDataDescriptor(51, {0, 1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {0, 1, 1}), MakeDataDescriptor(51, {0, 1, 1})));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b"
+               OpName %18 "level_1"
+               OpMemberName %18 0 "b1"
+               OpMemberName %18 1 "b2"
+               OpMemberName %18 2 "b3"
+               OpName %20 "l1"
+               OpName %25 "level_2"
+               OpMemberName %25 0 "c1"
+               OpMemberName %25 1 "c2"
+               OpName %27 "l2"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %18 = OpTypeStruct %12 %12 %12
+         %19 = OpTypePointer Function %18
+         %25 = OpTypeStruct %18 %18
+         %26 = OpTypePointer Function %25
+         %31 = OpTypeBool
+         %32 = OpConstantTrue %31
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %20 = OpVariable %19 Function
+         %27 = OpVariable %26 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %21 = OpLoad %12 %14
+         %22 = OpLoad %12 %14
+         %23 = OpLoad %12 %14
+         %24 = OpCompositeConstruct %18 %21 %22 %23
+               OpStore %20 %24
+         %28 = OpLoad %18 %20
+         %29 = OpLoad %18 %20
+         %30 = OpCompositeConstruct %25 %28 %29
+         %50 = OpCompositeInsert %25 %11 %30 1 0 0
+         %51 = OpCompositeInsert %25 %11 %50 0 1 1
+               OpStore %27 %30
+               OpSelectionMerge %34 None
+               OpBranchConditional %32 %33 %34
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationCompositeInsertTest, IdNotAvailableScenarios) {
+  // This test handles cases where either the composite or the object is not
+  // available before the |instruction_to_insert_before|.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "base"
+               OpMemberName %12 0 "a1"
+               OpMemberName %12 1 "a2"
+               OpName %14 "b1"
+               OpName %18 "b2"
+               OpName %22 "lvl1"
+               OpMemberName %22 0 "b1"
+               OpMemberName %22 1 "b2"
+               OpName %24 "l1"
+               OpName %28 "i3"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %12 = OpTypeStruct %6 %6
+         %13 = OpTypePointer Function %12
+         %22 = OpTypeStruct %12 %12
+         %23 = OpTypePointer Function %22
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %14 = OpVariable %13 Function
+         %18 = OpVariable %13 Function
+         %24 = OpVariable %23 Function
+         %28 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %15 = OpLoad %6 %8
+         %16 = OpLoad %6 %10
+         %17 = OpCompositeConstruct %12 %15 %16
+               OpStore %14 %17
+         %19 = OpLoad %6 %10
+         %20 = OpLoad %6 %8
+         %21 = OpCompositeConstruct %12 %19 %20
+               OpStore %18 %21
+         %25 = OpLoad %12 %14
+         %26 = OpLoad %12 %18
+         %27 = OpCompositeConstruct %22 %25 %26
+               OpStore %24 %27
+         %29 = OpLoad %6 %8
+         %30 = OpLoad %6 %10
+         %31 = OpIMul %6 %29 %30
+               OpStore %28 %31
+         %60 = OpCompositeConstruct %12 %20 %19
+         %61 = OpCompositeConstruct %22 %26 %25
+               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: The object with |object_id| is not available at
+  // |instruction_to_insert_before|.
+  auto transformation_bad_1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 27, 60, {1});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The composite with |composite_id| is not available at
+  // |instruction_to_insert_before|.
+  auto transformation_bad_2 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(31, SpvOpIMul, 0), 50, 61, 21, {1});
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The |instruction_to_insert_before| is the composite itself and is
+  // available.
+  auto transformation_bad_3 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(61, SpvOpCompositeConstruct, 0), 50, 61, 21,
+      {1});
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The |instruction_to_insert_before| is the object itself and is not
+  // available.
+  auto transformation_bad_4 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(60, SpvOpCompositeConstruct, 0), 50, 27, 60,
+      {1});
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationCompositeInsertTest, CompositeInsertionWithIrrelevantIds) {
+  // This checks that we do *not* get data synonym facts when we do composite
+  // insertion using irrelevant ids or in dead blocks.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeVector %6 2
+          %8 = OpConstant %6 0
+          %9 = OpConstantComposite %7 %8 %8
+         %10 = OpTypeBool
+         %11 = OpConstantFalse %10
+         %16 = OpConstant %6 0
+         %17 = OpConstant %6 1
+         %18 = OpConstantComposite %7 %8 %8
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %11 %14 %15
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, 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(14);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(16);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(18);
+
+  // Leads to synonyms - nothing is irrelevant.
+  auto transformation1 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(13, SpvOpSelectionMerge, 0), 100, 9, 17, {0});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {0}), MakeDataDescriptor(17, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {1}), MakeDataDescriptor(9, {1})));
+
+  // Because %16 is irrelevant, we don't get a synonym with the component to
+  // which it has been inserted (but we do for the other component).
+  auto transformation2 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(13, SpvOpSelectionMerge, 0), 101, 9, 16, {0});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(101, {0}), MakeDataDescriptor(16, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(101, {1}), MakeDataDescriptor(9, {1})));
+
+  // Because %18 is irrelevant we only get a synonym for the component into
+  // which insertion has taken place.
+  auto transformation3 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(13, SpvOpSelectionMerge, 0), 102, 18, 17, {0});
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(102, {0}), MakeDataDescriptor(17, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(102, {1}), MakeDataDescriptor(18, {1})));
+
+  // Does not lead to synonyms as block %14 is dead.
+  auto transformation4 = TransformationCompositeInsert(
+      MakeInstructionDescriptor(14, SpvOpBranch, 0), 103, 9, 17, {0});
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(103, {0}), MakeDataDescriptor(17, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(103, {1}), MakeDataDescriptor(9, {1})));
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp b/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp
index 5fa74b7..995b260 100644
--- a/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp
+++ b/test/fuzz/transformation_compute_data_synonym_fact_closure_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -42,7 +45,7 @@
   // void main() {
   //   T myT = T(bool[5](true, false, true, false, true),
   //             mat4x2(vec2(1.0, 2.0), vec2(3.0, 4.0),
-  // 	           vec2(5.0, 6.0), vec2(7.0, 8.0)),
+  //                    vec2(5.0, 6.0), vec2(7.0, 8.0)),
   //             S(10, uvec2(100u, 200u)));
   // }
 
@@ -120,256 +123,353 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_TRUE(TransformationComputeDataSynonymFactClosure(100).IsApplicable(
       context.get(), transformation_context));
 
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
-                                         MakeDataDescriptor(101, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
-                                         MakeDataDescriptor(101, {0})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {1}),
-                                         MakeDataDescriptor(101, {1})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
-                                         MakeDataDescriptor(101, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(101, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {0}), MakeDataDescriptor(101, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {1}), MakeDataDescriptor(101, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {0}), MakeDataDescriptor(101, {1})));
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(24, {}),
-                                  MakeDataDescriptor(101, {}), context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {}),
-                                        MakeDataDescriptor(101, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
-                                        MakeDataDescriptor(101, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {1}),
-                                        MakeDataDescriptor(101, {1})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(24, {0}),
-                                         MakeDataDescriptor(101, {1})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(101, {}));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {}), MakeDataDescriptor(101, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {0}), MakeDataDescriptor(101, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {1}), MakeDataDescriptor(101, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(24, {0}), MakeDataDescriptor(101, {1})));
 
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
-                                         MakeDataDescriptor(102, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
-                                         MakeDataDescriptor(102, {0})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
-                                         MakeDataDescriptor(102, {1})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {0}),
-                                  MakeDataDescriptor(102, {0}), context.get());
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
-                                         MakeDataDescriptor(102, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
-                                        MakeDataDescriptor(102, {0})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
-                                         MakeDataDescriptor(102, {1})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(27, {1}),
-                                  MakeDataDescriptor(102, {1}), context.get());
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {}), MakeDataDescriptor(102, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {0}), MakeDataDescriptor(102, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {1}), MakeDataDescriptor(102, {1})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(27, {0}), MakeDataDescriptor(102, {0}));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {}), MakeDataDescriptor(102, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {0}), MakeDataDescriptor(102, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {1}), MakeDataDescriptor(102, {1})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(27, {1}), MakeDataDescriptor(102, {1}));
 
-  TransformationComputeDataSynonymFactClosure(100).Apply(
-      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(TransformationComputeDataSynonymFactClosure(100),
+                        context.get(), &transformation_context);
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {}),
-                                        MakeDataDescriptor(102, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {0}),
-                                        MakeDataDescriptor(102, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(27, {1}),
-                                        MakeDataDescriptor(102, {1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {}), MakeDataDescriptor(102, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {0}), MakeDataDescriptor(102, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(27, {1}), MakeDataDescriptor(102, {1})));
 
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}),
-                                         MakeDataDescriptor(103, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
-                                         MakeDataDescriptor(103, {0})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}),
-                                         MakeDataDescriptor(103, {1})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
-                                         MakeDataDescriptor(104, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
-                                         MakeDataDescriptor(104, {0})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {1}),
-                                         MakeDataDescriptor(104, {1})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {}),
-                                         MakeDataDescriptor(105, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {0}),
-                                         MakeDataDescriptor(105, {0})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {1}),
-                                         MakeDataDescriptor(105, {1})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {2}),
-                                         MakeDataDescriptor(105, {2})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
-                                         MakeDataDescriptor(105, {3})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(30, {}),
-                                  MakeDataDescriptor(103, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(33, {}),
-                                  MakeDataDescriptor(104, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {0}),
-                                  MakeDataDescriptor(105, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {1}),
-                                  MakeDataDescriptor(105, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {2}),
-                                  MakeDataDescriptor(105, {2}), context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {}),
-                                        MakeDataDescriptor(103, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {0}),
-                                        MakeDataDescriptor(103, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(30, {1}),
-                                        MakeDataDescriptor(103, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {}),
-                                        MakeDataDescriptor(104, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
-                                        MakeDataDescriptor(104, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {1}),
-                                        MakeDataDescriptor(104, {1})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {}),
-                                         MakeDataDescriptor(105, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {0}),
-                                        MakeDataDescriptor(105, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {1}),
-                                        MakeDataDescriptor(105, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {2}),
-                                        MakeDataDescriptor(105, {2})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
-                                         MakeDataDescriptor(105, {3})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {}), MakeDataDescriptor(103, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {0}), MakeDataDescriptor(103, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1}), MakeDataDescriptor(103, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(33, {}), MakeDataDescriptor(104, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(33, {0}), MakeDataDescriptor(104, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(33, {1}), MakeDataDescriptor(104, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {}), MakeDataDescriptor(105, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {0}), MakeDataDescriptor(105, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {1}), MakeDataDescriptor(105, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {2}), MakeDataDescriptor(105, {2})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {3}), MakeDataDescriptor(105, {3})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(30, {}), MakeDataDescriptor(103, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(33, {}), MakeDataDescriptor(104, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(34, {0}), MakeDataDescriptor(105, {0}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(34, {1}), MakeDataDescriptor(105, {1}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(34, {2}), MakeDataDescriptor(105, {2}));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {}), MakeDataDescriptor(103, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {0}), MakeDataDescriptor(103, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(30, {1}), MakeDataDescriptor(103, {1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(33, {}), MakeDataDescriptor(104, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(33, {0}), MakeDataDescriptor(104, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(33, {1}), MakeDataDescriptor(104, {1})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {}), MakeDataDescriptor(105, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {0}), MakeDataDescriptor(105, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {1}), MakeDataDescriptor(105, {1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {2}), MakeDataDescriptor(105, {2})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {3}), MakeDataDescriptor(105, {3})));
 
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(34, {3}),
-                                  MakeDataDescriptor(105, {3}), context.get());
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(33, {0}),
-                                        MakeDataDescriptor(104, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(34, {3}),
-                                        MakeDataDescriptor(105, {3})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(34, {3}), MakeDataDescriptor(105, {3}));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(33, {0}), MakeDataDescriptor(104, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(34, {3}), MakeDataDescriptor(105, {3})));
 
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                         MakeDataDescriptor(100, {})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {0}),
-                                  MakeDataDescriptor(100, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {1}),
-                                  MakeDataDescriptor(100, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {2}),
-                                  MakeDataDescriptor(100, {2}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {3}),
-                                  MakeDataDescriptor(100, {3}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(21, {4}),
-                                  MakeDataDescriptor(100, {4}), context.get());
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(100, {})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(21, {0}), MakeDataDescriptor(100, {0}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(21, {1}), MakeDataDescriptor(100, {1}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(21, {2}), MakeDataDescriptor(100, {2}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(21, {3}), MakeDataDescriptor(100, {3}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(21, {4}), MakeDataDescriptor(100, {4}));
 
-  TransformationComputeDataSynonymFactClosure(100).Apply(
-      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(TransformationComputeDataSynonymFactClosure(100),
+                        context.get(), &transformation_context);
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                        MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(100, {})));
 
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(39, {0}),
-                                         MakeDataDescriptor(107, {0})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(35, {}),
-                                         MakeDataDescriptor(39, {0})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(39, {0}),
-                                  MakeDataDescriptor(35, {}), context.get());
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(39, {0}),
-                                         MakeDataDescriptor(107, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(35, {}),
-                                        MakeDataDescriptor(39, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(39, {0}), MakeDataDescriptor(107, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(35, {}), MakeDataDescriptor(39, {0})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(39, {0}), MakeDataDescriptor(35, {}));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(39, {0}), MakeDataDescriptor(107, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(35, {}), MakeDataDescriptor(39, {0})));
 
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {0}),
-                                         MakeDataDescriptor(36, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {1}),
-                                         MakeDataDescriptor(37, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {0}),
-                                         MakeDataDescriptor(36, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {1}),
-                                         MakeDataDescriptor(37, {})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {}),
-                                         MakeDataDescriptor(106, {})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {0}),
-                                  MakeDataDescriptor(36, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {0}),
-                                  MakeDataDescriptor(36, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(38, {1}),
-                                  MakeDataDescriptor(37, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(106, {1}),
-                                  MakeDataDescriptor(37, {}), context.get());
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {0}), MakeDataDescriptor(36, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {1}), MakeDataDescriptor(37, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(106, {0}), MakeDataDescriptor(36, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(106, {1}), MakeDataDescriptor(37, {})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {}), MakeDataDescriptor(106, {})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(38, {0}), MakeDataDescriptor(36, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(106, {0}), MakeDataDescriptor(36, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(38, {1}), MakeDataDescriptor(37, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(106, {1}), MakeDataDescriptor(37, {}));
 
-  TransformationComputeDataSynonymFactClosure(100).Apply(
-      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(TransformationComputeDataSynonymFactClosure(100),
+                        context.get(), &transformation_context);
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {0}),
-                                        MakeDataDescriptor(36, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {1}),
-                                        MakeDataDescriptor(37, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {0}),
-                                        MakeDataDescriptor(36, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(106, {1}),
-                                        MakeDataDescriptor(37, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(38, {}),
-                                        MakeDataDescriptor(106, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {0}), MakeDataDescriptor(36, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {1}), MakeDataDescriptor(37, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(106, {0}), MakeDataDescriptor(36, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(106, {1}), MakeDataDescriptor(37, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {}), MakeDataDescriptor(106, {})));
 
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}),
-                                         MakeDataDescriptor(108, {})));
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(107, {0}),
-                                  MakeDataDescriptor(35, {}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {0}),
-                                  MakeDataDescriptor(108, {0}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {1}),
-                                  MakeDataDescriptor(108, {1}), context.get());
-  fact_manager.AddFactDataSynonym(MakeDataDescriptor(40, {2}),
-                                  MakeDataDescriptor(108, {2}), context.get());
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(108, {})));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(107, {0}), MakeDataDescriptor(35, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {0}), MakeDataDescriptor(108, {0}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {1}), MakeDataDescriptor(108, {1}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {2}), MakeDataDescriptor(108, {2}));
 
-  TransformationComputeDataSynonymFactClosure(100).Apply(
-      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(TransformationComputeDataSynonymFactClosure(100),
+                        context.get(), &transformation_context);
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {}),
-                                        MakeDataDescriptor(108, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0}),
-                                        MakeDataDescriptor(108, {0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1}),
-                                        MakeDataDescriptor(108, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2}),
-                                        MakeDataDescriptor(108, {2})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 0}),
-                                        MakeDataDescriptor(108, {0, 0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 1}),
-                                        MakeDataDescriptor(108, {0, 1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 2}),
-                                        MakeDataDescriptor(108, {0, 2})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 3}),
-                                        MakeDataDescriptor(108, {0, 3})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {0, 4}),
-                                        MakeDataDescriptor(108, {0, 4})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0}),
-                                        MakeDataDescriptor(108, {1, 0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1}),
-                                        MakeDataDescriptor(108, {1, 1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2}),
-                                        MakeDataDescriptor(108, {1, 2})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3}),
-                                        MakeDataDescriptor(108, {1, 3})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0, 0}),
-                                        MakeDataDescriptor(108, {1, 0, 0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1, 0}),
-                                        MakeDataDescriptor(108, {1, 1, 0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2, 0}),
-                                        MakeDataDescriptor(108, {1, 2, 0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3, 0}),
-                                        MakeDataDescriptor(108, {1, 3, 0})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 0, 1}),
-                                        MakeDataDescriptor(108, {1, 0, 1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 1, 1}),
-                                        MakeDataDescriptor(108, {1, 1, 1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 2, 1}),
-                                        MakeDataDescriptor(108, {1, 2, 1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {1, 3, 1}),
-                                        MakeDataDescriptor(108, {1, 3, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(108, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {0}), MakeDataDescriptor(108, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1}), MakeDataDescriptor(108, {1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {2}), MakeDataDescriptor(108, {2})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {0, 0}), MakeDataDescriptor(108, {0, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {0, 1}), MakeDataDescriptor(108, {0, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {0, 2}), MakeDataDescriptor(108, {0, 2})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {0, 3}), MakeDataDescriptor(108, {0, 3})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {0, 4}), MakeDataDescriptor(108, {0, 4})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 0}), MakeDataDescriptor(108, {1, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 1}), MakeDataDescriptor(108, {1, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 2}), MakeDataDescriptor(108, {1, 2})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 3}), MakeDataDescriptor(108, {1, 3})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 0, 0}), MakeDataDescriptor(108, {1, 0, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 1, 0}), MakeDataDescriptor(108, {1, 1, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 2, 0}), MakeDataDescriptor(108, {1, 2, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 3, 0}), MakeDataDescriptor(108, {1, 3, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 0, 1}), MakeDataDescriptor(108, {1, 0, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 1, 1}), MakeDataDescriptor(108, {1, 1, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 2, 1}), MakeDataDescriptor(108, {1, 2, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {1, 3, 1}), MakeDataDescriptor(108, {1, 3, 1})));
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 0}),
-                                        MakeDataDescriptor(108, {2, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {2, 0}), MakeDataDescriptor(108, {2, 0})));
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1}),
-                                        MakeDataDescriptor(108, {2, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {2, 1}), MakeDataDescriptor(108, {2, 1})));
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1, 0}),
-                                        MakeDataDescriptor(108, {2, 1, 0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {2, 1, 0}), MakeDataDescriptor(108, {2, 1, 0})));
 
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(40, {2, 1, 1}),
-                                        MakeDataDescriptor(108, {2, 1, 1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(40, {2, 1, 1}), MakeDataDescriptor(108, {2, 1, 1})));
+}
+
+TEST(TransformationComputeDataSynonymFactClosureTest,
+     ComputeClosureWithMissingIds) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeVector %6 4
+         %15 = OpConstant %6 24
+         %16 = OpConstantComposite %7 %15 %15 %15 %15
+         %17 = OpConstantComposite %7 %15 %15 %15 %15
+         %18 = OpTypeStruct %7
+         %19 = OpConstantComposite %18 %16
+         %30 = OpConstantComposite %18 %17
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+         %50 = OpCopyObject %7 %16
+         %51 = OpCopyObject %7 %17
+         %20 = OpCopyObject %6 %15
+         %21 = OpCopyObject %6 %15
+         %22 = OpCopyObject %6 %15
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  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(20, {}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(22, {}), MakeDataDescriptor(15, {}));
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(17, {0}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(17, {1}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(17, {2}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(17, {3}), MakeDataDescriptor(15, {}));
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(16, {0}), MakeDataDescriptor(20, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(16, {1}), MakeDataDescriptor(21, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(16, {2}), MakeDataDescriptor(22, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(16, {3}), MakeDataDescriptor(15, {}));
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(51, {0}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(51, {1}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(51, {2}), MakeDataDescriptor(15, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(51, {3}), MakeDataDescriptor(15, {}));
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(50, {0}), MakeDataDescriptor(20, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(50, {1}), MakeDataDescriptor(21, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(50, {2}), MakeDataDescriptor(22, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(50, {3}), MakeDataDescriptor(15, {}));
+
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(19, {}), MakeDataDescriptor(30, {})));
+
+  context->KillDef(20);
+  context->KillDef(21);
+  context->KillDef(22);
+  context->KillDef(50);
+  context->KillDef(51);
+  context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  ApplyAndCheckFreshIds(TransformationComputeDataSynonymFactClosure(100),
+                        context.get(), &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(19, {}), MakeDataDescriptor(30, {})));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_duplicate_region_with_selection_test.cpp b/test/fuzz/transformation_duplicate_region_with_selection_test.cpp
new file mode 100644
index 0000000..f3738e7
--- /dev/null
+++ b/test/fuzz/transformation_duplicate_region_with_selection_test.cpp
@@ -0,0 +1,2280 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_duplicate_region_with_selection.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/counter_overflow_id_source.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationDuplicateRegionWithSelectionTest, BasicUseTest) {
+  // This test handles a case where the ids from the original region are used in
+  // subsequent block.
+
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "b"
+               OpName %18 "c"
+               OpName %20 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %14 = OpConstant %6 2
+         %16 = OpTypeBool
+         %17 = OpTypePointer Function %16
+         %19 = OpConstantTrue %16
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %18 = OpVariable %17 Function
+         %20 = OpVariable %7 Function
+               OpStore %18 %19
+               OpStore %20 %14
+         %21 = OpFunctionCall %2 %10 %20
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpBranch %800
+        %800 = OpLabel
+         %13 = OpLoad %6 %9
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+               OpBranch %900
+         %900 = OpLabel
+         %901 = OpIAdd %6 %15 %13
+         %902 = OpISub %6 %13 %15
+               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);
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  std::string expected_shader = R"(
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "b"
+               OpName %18 "c"
+               OpName %20 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %14 = OpConstant %6 2
+         %16 = OpTypeBool
+         %17 = OpTypePointer Function %16
+         %19 = OpConstantTrue %16
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %18 = OpVariable %17 Function
+         %20 = OpVariable %7 Function
+               OpStore %18 %19
+               OpStore %20 %14
+         %21 = OpFunctionCall %2 %10 %20
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpBranch %500
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %19 %800 %100
+        %800 = OpLabel
+         %13 = OpLoad %6 %9
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+               OpBranch %501
+        %100 = OpLabel
+        %201 = OpLoad %6 %9
+        %202 = OpIAdd %6 %201 %14
+               OpStore %12 %202
+               OpBranch %501
+        %501 = OpLabel
+        %301 = OpPhi %6 %13 %800 %201 %100
+        %302 = OpPhi %6 %15 %800 %202 %100
+               OpBranch %900
+        %900 = OpLabel
+        %901 = OpIAdd %6 %302 %301
+        %902 = OpISub %6 %301 %302
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, BasicExitBlockTest) {
+  // This test handles a case where the exit block of the region is the exit
+  // block of the containing function.
+
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "b"
+               OpName %18 "c"
+               OpName %20 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %14 = OpConstant %6 2
+         %16 = OpTypeBool
+         %17 = OpTypePointer Function %16
+         %19 = OpConstantTrue %16
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %18 = OpVariable %17 Function
+         %20 = OpVariable %7 Function
+               OpStore %18 %19
+               OpStore %20 %14
+         %21 = OpFunctionCall %2 %10 %20
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpBranch %800
+        %800 = OpLabel
+         %13 = OpLoad %6 %9
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+               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);
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+   OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "b"
+               OpName %18 "c"
+               OpName %20 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %14 = OpConstant %6 2
+         %16 = OpTypeBool
+         %17 = OpTypePointer Function %16
+         %19 = OpConstantTrue %16
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %18 = OpVariable %17 Function
+         %20 = OpVariable %7 Function
+               OpStore %18 %19
+               OpStore %20 %14
+         %21 = OpFunctionCall %2 %10 %20
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpBranch %500
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %19 %800 %100
+        %800 = OpLabel
+         %13 = OpLoad %6 %9
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+               OpBranch %501
+        %100 = OpLabel
+        %201 = OpLoad %6 %9
+        %202 = OpIAdd %6 %201 %14
+               OpStore %12 %202
+               OpBranch %501
+        %501 = OpLabel
+        %301 = OpPhi %6 %13 %800 %201 %100
+        %302 = OpPhi %6 %15 %800 %202 %100
+               OpReturn
+               OpFunctionEnd
+
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest) {
+  // This test handles few cases where the transformation is not applicable
+  // because of the control flow graph or layout of the blocks.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %18 "b"
+               OpName %25 "c"
+               OpName %27 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+         %24 = OpTypePointer Function %14
+         %26 = OpConstantTrue %14
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %25 = OpVariable %24 Function
+         %27 = OpVariable %7 Function
+               OpStore %25 %26
+               OpStore %27 %13
+         %28 = OpFunctionCall %2 %10 %27
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %18 = OpVariable %7 Function
+         %12 = OpLoad %6 %9
+         %15 = OpSLessThan %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %21
+         %16 = OpLabel
+         %19 = OpLoad %6 %9
+         %20 = OpIAdd %6 %19 %13
+               OpStore %18 %20
+               OpBranch %17
+         %21 = OpLabel
+         %22 = OpLoad %6 %9
+         %23 = OpISub %6 %22 %13
+               OpStore %18 %23
+               OpBranch %17
+         %17 = OpLabel
+               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: |entry_block_id| refers to the entry block of the function (this
+  // transformation currently avoids such cases).
+  TransformationDuplicateRegionWithSelection transformation_bad_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 26, 501, 11, 11, {{11, 100}}, {{18, 201}, {12, 202}, {15, 203}},
+          {{18, 301}, {12, 302}, {15, 303}});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The block with id 16 does not dominate the block with id 21.
+  TransformationDuplicateRegionWithSelection transformation_bad_2 =
+      TransformationDuplicateRegionWithSelection(
+          500, 26, 501, 16, 21, {{16, 100}, {21, 101}},
+          {{19, 201}, {20, 202}, {22, 203}, {23, 204}},
+          {{19, 301}, {20, 302}, {22, 303}, {23, 304}});
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The block with id 21 does not post-dominate the block with id 11.
+  TransformationDuplicateRegionWithSelection transformation_bad_3 =
+      TransformationDuplicateRegionWithSelection(
+          500, 26, 501, 11, 21, {{11, 100}, {21, 101}},
+          {{18, 201}, {12, 202}, {15, 203}, {22, 204}, {23, 205}},
+          {{18, 301}, {12, 302}, {15, 303}, {22, 304}, {23, 305}});
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The block with id 5 is contained in a different function than the
+  // block with id 11.
+  TransformationDuplicateRegionWithSelection transformation_bad_4 =
+      TransformationDuplicateRegionWithSelection(
+          500, 26, 501, 5, 11, {{5, 100}, {11, 101}},
+          {{25, 201}, {27, 202}, {28, 203}, {18, 204}, {12, 205}, {15, 206}},
+          {{25, 301}, {27, 302}, {28, 303}, {18, 304}, {12, 305}, {15, 306}});
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableIdTest) {
+  // This test handles a case where the supplied ids are either not fresh, not
+  // distinct, not valid in their context or do not refer to the existing
+  // instructions.
+
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "b"
+               OpName %18 "c"
+               OpName %20 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %14 = OpConstant %6 2
+         %16 = OpTypeBool
+         %17 = OpTypePointer Function %16
+         %19 = OpConstantTrue %16
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %18 = OpVariable %17 Function
+         %20 = OpVariable %7 Function
+               OpStore %18 %19
+               OpStore %20 %14
+         %21 = OpFunctionCall %2 %10 %20
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpBranch %800
+        %800 = OpLabel
+         %13 = OpLoad %6 %9
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+               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: A value in the |original_label_to_duplicate_label| is not a fresh id.
+  TransformationDuplicateRegionWithSelection transformation_bad_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 800, 800, {{800, 21}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Values in the |original_id_to_duplicate_id| are not distinct.
+  TransformationDuplicateRegionWithSelection transformation_bad_2 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 201}},
+          {{13, 301}, {15, 302}});
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Values in the |original_id_to_phi_id| are not fresh and are not
+  // distinct with previous values.
+  TransformationDuplicateRegionWithSelection transformation_bad_3 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 18}, {15, 202}});
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |entry_block_id| does not refer to an existing instruction.
+  TransformationDuplicateRegionWithSelection transformation_bad_4 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 802, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |exit_block_id| does not refer to a block.
+  TransformationDuplicateRegionWithSelection transformation_bad_5 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 800, 9, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+  ASSERT_FALSE(
+      transformation_bad_5.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |new_entry_fresh_id| is not fresh.
+  TransformationDuplicateRegionWithSelection transformation_bad_6 =
+      TransformationDuplicateRegionWithSelection(
+          20, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+  ASSERT_FALSE(
+      transformation_bad_6.IsApplicable(context.get(), transformation_context));
+
+  // Bad: |merge_label_fresh_id| is not fresh.
+  TransformationDuplicateRegionWithSelection transformation_bad_7 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 20, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+  ASSERT_FALSE(
+      transformation_bad_7.IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  // Bad: Instruction with id 15 is from the original region and is available
+  // at the end of the region but it is not present in the
+  // |original_id_to_phi_id|.
+  TransformationDuplicateRegionWithSelection transformation_bad_8 =
+      TransformationDuplicateRegionWithSelection(
+          500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}});
+  ASSERT_DEATH(
+      transformation_bad_8.IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+
+  // Bad: Instruction with id 15 is from the original region but it is
+  // not present in the |original_id_to_duplicate_id|.
+  TransformationDuplicateRegionWithSelection transformation_bad_9 =
+      TransformationDuplicateRegionWithSelection(500, 19, 501, 800, 800,
+                                                 {{800, 100}}, {{13, 201}},
+                                                 {{13, 301}, {15, 302}});
+  ASSERT_DEATH(
+      transformation_bad_9.IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  // Bad: |condition_id| does not refer to the valid instruction.
+  TransformationDuplicateRegionWithSelection transformation_bad_10 =
+      TransformationDuplicateRegionWithSelection(
+          500, 200, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+
+  ASSERT_FALSE(transformation_bad_10.IsApplicable(context.get(),
+                                                  transformation_context));
+
+  // Bad: |condition_id| does not refer to the instruction of type OpTypeBool
+  TransformationDuplicateRegionWithSelection transformation_bad_11 =
+      TransformationDuplicateRegionWithSelection(
+          500, 14, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+          {{13, 301}, {15, 302}});
+
+  ASSERT_FALSE(transformation_bad_11.IsApplicable(context.get(),
+                                                  transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest2) {
+  // This test handles few cases where the transformation is not applicable
+  // because of the control flow graph or the layout of the blocks.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %12 "i"
+               OpName %29 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %19 = OpConstant %8 10
+         %20 = OpTypeBool
+         %26 = OpConstant %8 1
+         %28 = OpTypePointer Function %20
+         %30 = OpConstantTrue %20
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %29 = OpVariable %28 Function
+               OpStore %29 %30
+         %31 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+         %12 = OpVariable %9 Function
+               OpStore %10 %11
+               OpStore %12 %11
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %15 %16 None
+               OpBranch %17
+         %17 = OpLabel
+         %18 = OpLoad %8 %12
+         %21 = OpSLessThan %20 %18 %19
+               OpBranchConditional %21 %14 %15
+         %14 = OpLabel
+         %22 = OpLoad %8 %10
+         %23 = OpLoad %8 %12
+         %24 = OpIAdd %8 %22 %23
+               OpStore %10 %24
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %50
+         %50 = OpLabel
+         %25 = OpLoad %8 %12
+         %27 = OpIAdd %8 %25 %26
+               OpStore %12 %27
+               OpBranch %13
+         %15 = OpLabel
+               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: The exit block cannot be a header of a loop, because the region won't
+  // be a single-entry, single-exit region.
+  TransformationDuplicateRegionWithSelection transformation_bad_1 =
+      TransformationDuplicateRegionWithSelection(500, 30, 501, 13, 13,
+                                                 {{13, 100}}, {{}}, {{}});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The block with id 13, the loop header, is in the region. The block
+  // with id 15, the loop merge block, is not in the region.
+  TransformationDuplicateRegionWithSelection transformation_bad_2 =
+      TransformationDuplicateRegionWithSelection(
+          500, 30, 501, 13, 17, {{13, 100}, {17, 101}}, {{18, 201}, {21, 202}},
+          {{18, 301}, {21, 302}});
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The block with id 13, the loop header, is not in the region. The block
+  // with id 16, the loop continue target, is in the region.
+  TransformationDuplicateRegionWithSelection transformation_bad_3 =
+      TransformationDuplicateRegionWithSelection(
+          500, 30, 501, 16, 50, {{16, 100}, {50, 101}}, {{25, 201}, {27, 202}},
+          {{25, 301}, {27, 302}});
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest3) {
+  // This test handles a case where for the block which is not the exit block,
+  // not all successors are in the region.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %14 "a"
+               OpName %19 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeBool
+          %9 = OpConstantTrue %8
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 2
+         %17 = OpConstant %12 3
+         %18 = OpTypePointer Function %8
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpVariable %18 Function
+               OpStore %19 %9
+         %20 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %14 = OpVariable %13 Function
+               OpSelectionMerge %11 None
+               OpBranchConditional %9 %10 %16
+         %10 = OpLabel
+               OpStore %14 %15
+               OpBranch %11
+         %16 = OpLabel
+               OpStore %14 %17
+               OpBranch %11
+         %11 = OpLabel
+               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: The block with id 7, which is not an exit block, has two successors:
+  // the block with id 10 and the block with id 16. The block with id 16 is not
+  // in the region.
+  TransformationDuplicateRegionWithSelection transformation_bad_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 30, 501, 7, 10, {{13, 100}}, {{14, 201}}, {{14, 301}});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, MultipleBlocksLoopTest) {
+  // This test handles a case where the region consists of multiple blocks
+  // (they form a loop). The transformation is applicable and the region is
+  // duplicated.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %12 "i"
+               OpName %29 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %19 = OpConstant %8 10
+         %20 = OpTypeBool
+         %26 = OpConstant %8 1
+         %28 = OpTypePointer Function %20
+         %30 = OpConstantTrue %20
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %29 = OpVariable %28 Function
+               OpStore %29 %30
+         %31 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+         %12 = OpVariable %9 Function
+               OpStore %10 %11
+               OpStore %12 %11
+               OpBranch %50
+         %50 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %15 %16 None
+               OpBranch %17
+         %17 = OpLabel
+         %18 = OpLoad %8 %12
+         %21 = OpSLessThan %20 %18 %19
+               OpBranchConditional %21 %14 %15
+         %14 = OpLabel
+         %22 = OpLoad %8 %10
+         %23 = OpLoad %8 %12
+         %24 = OpIAdd %8 %22 %23
+               OpStore %10 %24
+               OpBranch %16
+         %16 = OpLabel
+         %25 = OpLoad %8 %12
+         %27 = OpIAdd %8 %25 %26
+               OpStore %12 %27
+               OpBranch %13
+         %15 = OpLabel
+               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);
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 30, 501, 50, 15,
+          {{50, 100}, {13, 101}, {14, 102}, {15, 103}, {16, 104}, {17, 105}},
+          {{22, 201},
+           {23, 202},
+           {24, 203},
+           {25, 204},
+           {27, 205},
+           {18, 206},
+           {21, 207}},
+          {{22, 301},
+           {23, 302},
+           {24, 303},
+           {25, 304},
+           {27, 305},
+           {18, 306},
+           {21, 307}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+                 OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %12 "i"
+               OpName %29 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %19 = OpConstant %8 10
+         %20 = OpTypeBool
+         %26 = OpConstant %8 1
+         %28 = OpTypePointer Function %20
+         %30 = OpConstantTrue %20
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %29 = OpVariable %28 Function
+               OpStore %29 %30
+         %31 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+         %12 = OpVariable %9 Function
+               OpStore %10 %11
+               OpStore %12 %11
+               OpBranch %500
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %30 %50 %100
+         %50 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+               OpLoopMerge %15 %16 None
+               OpBranch %17
+         %17 = OpLabel
+         %18 = OpLoad %8 %12
+         %21 = OpSLessThan %20 %18 %19
+               OpBranchConditional %21 %14 %15
+         %14 = OpLabel
+         %22 = OpLoad %8 %10
+         %23 = OpLoad %8 %12
+         %24 = OpIAdd %8 %22 %23
+               OpStore %10 %24
+               OpBranch %16
+         %16 = OpLabel
+         %25 = OpLoad %8 %12
+         %27 = OpIAdd %8 %25 %26
+               OpStore %12 %27
+               OpBranch %13
+         %15 = OpLabel
+               OpBranch %501
+        %100 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpLoopMerge %103 %104 None
+               OpBranch %105
+        %105 = OpLabel
+        %206 = OpLoad %8 %12
+        %207 = OpSLessThan %20 %206 %19
+               OpBranchConditional %207 %102 %103
+        %102 = OpLabel
+        %201 = OpLoad %8 %10
+        %202 = OpLoad %8 %12
+        %203 = OpIAdd %8 %201 %202
+               OpStore %10 %203
+               OpBranch %104
+        %104 = OpLabel
+        %204 = OpLoad %8 %12
+        %205 = OpIAdd %8 %204 %26
+               OpStore %12 %205
+               OpBranch %101
+        %103 = OpLabel
+               OpBranch %501
+        %501 = OpLabel
+        %306 = OpPhi %8 %18 %15 %206 %103
+        %307 = OpPhi %20 %21 %15 %207 %103
+               OpReturn
+               OpFunctionEnd
+    )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     ResolvingOpPhiExitBlockTest) {
+  // This test handles a case where the region under the transformation is
+  // referenced in OpPhi instructions. Since the new merge block becomes the
+  // exit of the region, these OpPhi instructions need to be updated.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %26 "b"
+               OpName %29 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 0
+         %15 = OpConstant %6 2
+         %16 = OpTypeBool
+         %25 = OpTypePointer Function %16
+         %27 = OpConstantTrue %16
+         %28 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpVariable %25 Function
+         %29 = OpVariable %7 Function
+               OpStore %26 %27
+               OpStore %29 %28
+         %30 = OpFunctionCall %2 %10 %29
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpStore %12 %13
+         %14 = OpLoad %6 %9
+         %17 = OpSLessThan %16 %14 %15
+               OpSelectionMerge %19 None
+               OpBranchConditional %17 %18 %22
+         %18 = OpLabel
+         %20 = OpLoad %6 %9
+         %21 = OpIAdd %6 %20 %15
+               OpStore %12 %21
+               OpBranch %19
+         %22 = OpLabel
+         %23 = OpLoad %6 %9
+         %24 = OpIMul %6 %23 %15
+               OpStore %12 %24
+               OpBranch %19
+         %19 = OpLabel
+         %40 = OpPhi %6 %21 %18 %24 %22
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 27, 501, 22, 22, {{22, 100}}, {{23, 201}, {24, 202}},
+          {{23, 301}, {24, 302}});
+
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %26 "b"
+               OpName %29 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 0
+         %15 = OpConstant %6 2
+         %16 = OpTypeBool
+         %25 = OpTypePointer Function %16
+         %27 = OpConstantTrue %16
+         %28 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %26 = OpVariable %25 Function
+         %29 = OpVariable %7 Function
+               OpStore %26 %27
+               OpStore %29 %28
+         %30 = OpFunctionCall %2 %10 %29
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpStore %12 %13
+         %14 = OpLoad %6 %9
+         %17 = OpSLessThan %16 %14 %15
+               OpSelectionMerge %19 None
+               OpBranchConditional %17 %18 %500
+         %18 = OpLabel
+         %20 = OpLoad %6 %9
+         %21 = OpIAdd %6 %20 %15
+               OpStore %12 %21
+               OpBranch %19
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %27 %22 %100
+         %22 = OpLabel
+         %23 = OpLoad %6 %9
+         %24 = OpIMul %6 %23 %15
+               OpStore %12 %24
+               OpBranch %501
+        %100 = OpLabel
+        %201 = OpLoad %6 %9
+        %202 = OpIMul %6 %201 %15
+               OpStore %12 %202
+               OpBranch %501
+        %501 = OpLabel
+        %301 = OpPhi %6 %23 %22 %201 %100
+        %302 = OpPhi %6 %24 %22 %202 %100
+               OpBranch %19
+         %19 = OpLabel
+         %40 = OpPhi %6 %21 %18 %302 %501
+               OpReturn
+               OpFunctionEnd
+    )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableEarlyReturn) {
+  // This test handles a case where one of the blocks has successor outside of
+  // the region, which has an early return from the function, so that the
+  // transformation is not applicable.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %27 "b"
+               OpName %30 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 0
+         %15 = OpConstant %6 2
+         %16 = OpTypeBool
+         %26 = OpTypePointer Function %16
+         %28 = OpConstantTrue %16
+         %29 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %27 = OpVariable %26 Function
+         %30 = OpVariable %7 Function
+               OpStore %27 %28
+               OpStore %30 %29
+         %31 = OpFunctionCall %2 %10 %30
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+               OpBranch %50
+         %50 = OpLabel
+               OpStore %12 %13
+         %14 = OpLoad %6 %9
+         %17 = OpSLessThan %16 %14 %15
+               OpSelectionMerge %19 None
+               OpBranchConditional %17 %18 %22
+         %18 = OpLabel
+         %20 = OpLoad %6 %9
+         %21 = OpIAdd %6 %20 %15
+               OpStore %12 %21
+               OpBranch %19
+         %22 = OpLabel
+               OpReturn
+         %19 = OpLabel
+               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: The block with id 50, which is the entry block, has two successors:
+  // the block with id 18 and the block with id 22. The block 22 has an early
+  // return from the function, so that the entry block is not post-dominated by
+  // the exit block.
+  TransformationDuplicateRegionWithSelection transformation_bad_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 28, 501, 50, 19, {{50, 100}, {18, 101}, {22, 102}, {19, 103}},
+          {{14, 202}, {17, 203}, {20, 204}, {21, 205}},
+          {{14, 302}, {17, 303}, {20, 304}, {21, 305}});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     ResolvingOpPhiEntryBlockOnePredecessor) {
+  // This test handles a case where the entry block has an OpPhi instruction
+  // referring to its predecessor. After transformation, this instruction needs
+  // to be updated.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %14 "t"
+               OpName %20 "b"
+               OpName %23 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 0
+         %15 = OpConstant %6 2
+         %18 = OpTypeBool
+         %19 = OpTypePointer Function %18
+         %21 = OpConstantTrue %18
+         %22 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %19 Function
+         %23 = OpVariable %7 Function
+               OpStore %20 %21
+               OpStore %23 %22
+         %24 = OpFunctionCall %2 %10 %23
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %12 %13
+         %16 = OpLoad %6 %12
+         %17 = OpIMul %6 %15 %16
+               OpStore %14 %17
+               OpBranch %50
+         %50 = OpLabel
+         %51 = OpPhi %6 %17 %11
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 21, 501, 50, 50, {{50, 100}}, {{51, 201}}, {{51, 301}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %14 "t"
+               OpName %20 "b"
+               OpName %23 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 0
+         %15 = OpConstant %6 2
+         %18 = OpTypeBool
+         %19 = OpTypePointer Function %18
+         %21 = OpConstantTrue %18
+         %22 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %19 Function
+         %23 = OpVariable %7 Function
+               OpStore %20 %21
+               OpStore %23 %22
+         %24 = OpFunctionCall %2 %10 %23
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %12 %13
+         %16 = OpLoad %6 %12
+         %17 = OpIMul %6 %15 %16
+               OpStore %14 %17
+               OpBranch %500
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %21 %50 %100
+         %50 = OpLabel
+         %51 = OpPhi %6 %17 %500
+               OpBranch %501
+        %100 = OpLabel
+        %201 = OpPhi %6 %17 %500
+               OpBranch %501
+        %501 = OpLabel
+        %301 = OpPhi %6 %51 %50 %201 %100
+               OpReturn
+               OpFunctionEnd
+    )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     NotApplicableNoVariablePointerCapability) {
+  // This test handles a case where the transformation would create an OpPhi
+  // instruction with pointer operands, however there is no cab
+  // CapabilityVariablePointers. Hence, the transformation is not applicable.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %14 "t"
+               OpName %20 "b"
+               OpName %23 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 0
+         %15 = OpConstant %6 2
+         %18 = OpTypeBool
+         %19 = OpTypePointer Function %18
+         %21 = OpConstantTrue %18
+         %22 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpVariable %19 Function
+         %23 = OpVariable %7 Function
+               OpStore %20 %21
+               OpStore %23 %22
+         %24 = OpFunctionCall %2 %10 %23
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %14 = OpVariable %7 Function
+               OpStore %12 %13
+         %16 = OpLoad %6 %12
+         %17 = OpIMul %6 %15 %16
+               OpStore %14 %17
+               OpBranch %50
+         %50 = OpLabel
+         %51 = OpCopyObject %7 %12
+               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: There is no required capability CapabilityVariablePointers
+  TransformationDuplicateRegionWithSelection transformation_bad_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 21, 501, 50, 50, {{50, 100}}, {{51, 201}}, {{51, 301}});
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     ExitBlockTerminatorOpUnreachable) {
+  // This test handles a case where the exit block ends with OpUnreachable.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %17 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %13 = OpConstant %8 2
+         %15 = OpTypeBool
+         %16 = OpTypePointer Function %15
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %17 = OpVariable %16 Function
+               OpStore %17 %18
+         %19 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+               OpBranch %50
+         %50 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %8 %10
+         %14 = OpIAdd %8 %12 %13
+               OpStore %10 %14
+               OpUnreachable
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 18, 501, 50, 50, {{50, 100}}, {{12, 201}, {14, 202}},
+          {{12, 301}, {14, 302}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %17 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %13 = OpConstant %8 2
+         %15 = OpTypeBool
+         %16 = OpTypePointer Function %15
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %17 = OpVariable %16 Function
+               OpStore %17 %18
+         %19 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+               OpBranch %500
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %18 %50 %100
+         %50 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %8 %10
+         %14 = OpIAdd %8 %12 %13
+               OpStore %10 %14
+               OpBranch %501
+        %100 = OpLabel
+               OpStore %10 %11
+        %201 = OpLoad %8 %10
+        %202 = OpIAdd %8 %201 %13
+               OpStore %10 %202
+               OpBranch %501
+        %501 = OpLabel
+        %301 = OpPhi %8 %12 %50 %201 %100
+        %302 = OpPhi %8 %14 %50 %202 %100
+               OpUnreachable
+               OpFunctionEnd
+        )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     ExitBlockTerminatorOpKill) {
+  // This test handles a case where the exit block ends with OpKill.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %17 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %13 = OpConstant %8 2
+         %15 = OpTypeBool
+         %16 = OpTypePointer Function %15
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %17 = OpVariable %16 Function
+               OpStore %17 %18
+         %19 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+               OpBranch %50
+         %50 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %8 %10
+         %14 = OpIAdd %8 %12 %13
+               OpStore %10 %14
+               OpKill
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          500, 18, 501, 50, 50, {{50, 100}}, {{12, 201}, {14, 202}},
+          {{12, 301}, {14, 302}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %17 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %13 = OpConstant %8 2
+         %15 = OpTypeBool
+         %16 = OpTypePointer Function %15
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %17 = OpVariable %16 Function
+               OpStore %17 %18
+         %19 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+               OpBranch %500
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %18 %50 %100
+         %50 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %8 %10
+         %14 = OpIAdd %8 %12 %13
+               OpStore %10 %14
+               OpBranch %501
+        %100 = OpLabel
+               OpStore %10 %11
+        %201 = OpLoad %8 %10
+        %202 = OpIAdd %8 %201 %13
+               OpStore %10 %202
+               OpBranch %501
+        %501 = OpLabel
+        %301 = OpPhi %8 %12 %50 %201 %100
+        %302 = OpPhi %8 %14 %50 %202 %100
+               OpKill
+               OpFunctionEnd
+        )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     ContinueExitBlockNotApplicable) {
+  // This test handles a case where the exit block is the continue target and
+  // the transformation is not applicable.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "s"
+               OpName %10 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %17 = OpConstant %6 10
+         %18 = OpTypeBool
+         %24 = OpConstant %6 5
+         %30 = OpConstant %6 1
+         %50 = OpConstantTrue %18
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %9
+               OpBranch %11
+         %11 = OpLabel
+               OpLoopMerge %13 %14 None
+               OpBranch %15
+         %15 = OpLabel
+         %16 = OpLoad %6 %10
+         %19 = OpSLessThan %18 %16 %17
+               OpBranchConditional %19 %12 %13
+         %12 = OpLabel
+         %20 = OpLoad %6 %10
+         %21 = OpLoad %6 %8
+         %22 = OpIAdd %6 %21 %20
+               OpStore %8 %22
+         %23 = OpLoad %6 %10
+         %25 = OpIEqual %18 %23 %24
+               OpSelectionMerge %27 None
+               OpBranchConditional %25 %26 %27
+         %26 = OpLabel
+               OpBranch %13
+         %27 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+         %29 = OpLoad %6 %10
+         %31 = OpIAdd %6 %29 %30
+               OpStore %10 %31
+               OpBranch %11
+         %13 = OpLabel
+               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);
+  TransformationDuplicateRegionWithSelection transformation_bad =
+      TransformationDuplicateRegionWithSelection(
+          500, 50, 501, 27, 14, {{27, 101}, {14, 102}}, {{29, 201}, {31, 202}},
+          {{29, 301}, {31, 302}});
+
+  ASSERT_FALSE(
+      transformation_bad.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     MultiplePredecessorsNotApplicableTest) {
+  // This test handles a case where the entry block has multiple predecessors
+  // and the transformation is not applicable.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %10 "fun1(i1;"
+               OpName %9 "a"
+               OpName %18 "b"
+               OpName %24 "b"
+               OpName %27 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %2 %7
+         %13 = OpConstant %6 2
+         %14 = OpTypeBool
+         %23 = OpTypePointer Function %14
+         %25 = OpConstantTrue %14
+         %26 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %24 = OpVariable %23 Function
+         %27 = OpVariable %7 Function
+               OpStore %24 %25
+               OpStore %27 %26
+         %28 = OpFunctionCall %2 %10 %27
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %18 = OpVariable %7 Function
+         %12 = OpLoad %6 %9
+         %15 = OpSLessThan %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %20
+         %16 = OpLabel
+         %19 = OpLoad %6 %9
+               OpStore %18 %19
+               OpBranch %60
+         %20 = OpLabel
+         %21 = OpLoad %6 %9
+         %22 = OpIAdd %6 %21 %13
+               OpStore %18 %22
+               OpBranch %60
+         %60 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_bad =
+      TransformationDuplicateRegionWithSelection(500, 25, 501, 60, 60,
+                                                 {{60, 101}}, {{}}, {{}});
+
+  ASSERT_FALSE(
+      transformation_bad.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, OverflowIds) {
+  // This test checks that the transformation correctly uses overflow ids, when
+  // they are both needed and provided.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %17 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %13 = OpConstant %8 2
+         %15 = OpTypeBool
+         %16 = OpTypePointer Function %15
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %17 = OpVariable %16 Function
+               OpStore %17 %18
+         %19 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+               OpBranch %50
+         %50 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %8 %10
+         %14 = OpIAdd %8 %12 %13
+               OpStore %10 %14
+               OpKill
+               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));
+  auto overflow_ids_unique_ptr = MakeUnique<CounterOverflowIdSource>(1000);
+  auto overflow_ids_ptr = overflow_ids_unique_ptr.get();
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options,
+      std::move(overflow_ids_unique_ptr));
+
+  // The mappings do not provide sufficient ids, thus overflow ids are required.
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(500, 18, 501, 50, 50, {},
+                                                 {{12, 201}}, {{14, 302}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context,
+                        overflow_ids_ptr->GetIssuedOverflowIds());
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %6 "fun("
+               OpName %10 "s"
+               OpName %17 "b"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %11 = OpConstant %8 0
+         %13 = OpConstant %8 2
+         %15 = OpTypeBool
+         %16 = OpTypePointer Function %15
+         %18 = OpConstantTrue %15
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %17 = OpVariable %16 Function
+               OpStore %17 %18
+         %19 = OpFunctionCall %2 %6
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+         %10 = OpVariable %9 Function
+               OpBranch %500
+        %500 = OpLabel
+               OpSelectionMerge %501 None
+               OpBranchConditional %18 %50 %1000
+         %50 = OpLabel
+               OpStore %10 %11
+         %12 = OpLoad %8 %10
+         %14 = OpIAdd %8 %12 %13
+               OpStore %10 %14
+               OpBranch %501
+       %1000 = OpLabel
+               OpStore %10 %11
+        %201 = OpLoad %8 %10
+       %1002 = OpIAdd %8 %201 %13
+               OpStore %10 %1002
+               OpBranch %501
+        %501 = OpLabel
+       %1001 = OpPhi %8 %12 %50 %201 %1000
+        %302 = OpPhi %8 %14 %50 %1002 %1000
+               OpKill
+               OpFunctionEnd
+        )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     RegionExitIsOpBranchConditional) {
+  // Checks the case where the exit block of a region ends with
+  // OpBranchConditional (but is not a header).
+  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 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %50 = OpConstantTrue %17
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+               OpStore %8 %21
+               OpBranchConditional %50 %13 %12
+         %13 = OpLabel
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %20
+               OpStore %8 %23
+               OpBranch %10
+         %12 = OpLabel
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          600, 50, 601, 11, 11, {{11, 602}}, {{19, 603}, {21, 604}},
+          {{19, 605}, {21, 606}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_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 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %50 = OpConstantTrue %17
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %600 %12
+        %600 = OpLabel
+               OpSelectionMerge %601 None
+               OpBranchConditional %50 %11 %602
+         %11 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+               OpStore %8 %21
+               OpBranch %601
+        %602 = OpLabel
+        %603 = OpLoad %6 %8
+        %604 = OpIAdd %6 %603 %20
+               OpStore %8 %604
+               OpBranch %601
+        %601 = OpLabel
+        %605 = OpPhi %6 %19 %11 %603 %602
+        %606 = OpPhi %6 %21 %11 %604 %602
+               OpBranchConditional %50 %13 %12
+         %13 = OpLabel
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %20
+               OpStore %8 %23
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     RegionExitIsOpBranchConditionalUsingBooleanDefinedInBlock) {
+  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 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %50 = OpConstantTrue %17
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %11 %12
+         %11 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+         %70 = OpCopyObject %17 %50
+               OpStore %8 %21
+               OpBranchConditional %70 %13 %12
+         %13 = OpLabel
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %20
+               OpStore %8 %23
+               OpBranch %10
+         %12 = OpLabel
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          600, 50, 601, 11, 11, {{11, 602}}, {{19, 603}, {21, 604}, {70, 608}},
+          {{19, 605}, {21, 606}, {70, 607}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_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 "i"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %16 = OpConstant %6 100
+         %17 = OpTypeBool
+         %50 = OpConstantTrue %17
+         %20 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpStore %8 %9
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %12 %13 None
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpLoad %6 %8
+         %18 = OpSLessThan %17 %15 %16
+               OpBranchConditional %18 %600 %12
+        %600 = OpLabel
+               OpSelectionMerge %601 None
+               OpBranchConditional %50 %11 %602
+         %11 = OpLabel
+         %19 = OpLoad %6 %8
+         %21 = OpIAdd %6 %19 %20
+         %70 = OpCopyObject %17 %50
+               OpStore %8 %21
+               OpBranch %601
+        %602 = OpLabel
+        %603 = OpLoad %6 %8
+        %604 = OpIAdd %6 %603 %20
+        %608 = OpCopyObject %17 %50
+               OpStore %8 %604
+               OpBranch %601
+        %601 = OpLabel
+        %605 = OpPhi %6 %19 %11 %603 %602
+        %606 = OpPhi %6 %21 %11 %604 %602
+        %607 = OpPhi %17 %70 %11 %608 %602
+               OpBranchConditional %607 %13 %12
+         %13 = OpLabel
+         %22 = OpLoad %6 %8
+         %23 = OpIAdd %6 %22 %20
+               OpStore %8 %23
+               OpBranch %10
+         %12 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     RegionExitUsesOpReturnValueWithIdDefinedInRegion) {
+  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 = OpTypeFunction %6
+         %10 = OpConstant %6 2
+         %30 = OpTypeBool
+         %31 = OpConstantTrue %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpFunctionCall %6 %8
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpCopyObject %6 %10
+               OpReturnValue %21
+               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);
+
+  TransformationDuplicateRegionWithSelection transformation_good_1 =
+      TransformationDuplicateRegionWithSelection(
+          600, 31, 601, 20, 20, {{20, 602}}, {{21, 603}}, {{21, 605}});
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string expected_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 = OpTypeFunction %6
+         %10 = OpConstant %6 2
+         %30 = OpTypeBool
+         %31 = OpConstantTrue %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpFunctionCall %6 %8
+               OpReturn
+               OpFunctionEnd
+          %8 = OpFunction %6 None %7
+          %9 = OpLabel
+               OpBranch %600
+        %600 = OpLabel
+               OpSelectionMerge %601 None
+               OpBranchConditional %31 %20 %602
+         %20 = OpLabel
+         %21 = OpCopyObject %6 %10
+               OpBranch %601
+        %602 = OpLabel
+        %603 = OpCopyObject %6 %10
+               OpBranch %601
+        %601 = OpLabel
+        %605 = OpPhi %6 %21 %20 %603 %602
+               OpReturnValue %605
+               OpFunctionEnd
+        )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+     InapplicableDueToOpTypeSampledImage) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %10
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %10 DescriptorSet 0
+               OpDecorate %10 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeImage %6 2D 0 0 0 1 Unknown
+          %8 = OpTypeSampledImage %7
+          %9 = OpTypePointer UniformConstant %8
+         %10 = OpVariable %9 UniformConstant
+         %12 = OpTypeVector %6 2
+         %13 = OpConstant %6 0
+         %14 = OpConstantComposite %12 %13 %13
+         %15 = OpTypeVector %6 4
+         %30 = OpTypeBool
+         %31 = OpConstantTrue %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %11 = OpLoad %8 %10
+               OpBranch %21
+         %21 = OpLabel
+         %16 = OpImageSampleImplicitLod %15 %11 %14
+               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);
+
+  ASSERT_FALSE(TransformationDuplicateRegionWithSelection(
+                   600, 31, 601, 20, 20, {{20, 602}}, {{11, 603}}, {{11, 605}})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_equation_instruction_test.cpp b/test/fuzz/transformation_equation_instruction_test.cpp
index 132e446..654fffc 100644
--- a/test/fuzz/transformation_equation_instruction_test.cpp
+++ b/test/fuzz/transformation_equation_instruction_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_equation_instruction.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -46,13 +48,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -102,15 +102,19 @@
       14, SpvOpSNegate, {7}, return_instruction);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation2 = TransformationEquationInstruction(
       15, SpvOpSNegate, {14}, return_instruction);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(15, {}), MakeDataDescriptor(7, {})));
@@ -164,13 +168,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -193,15 +195,19 @@
       14, SpvOpLogicalNot, {7}, return_instruction);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation2 = TransformationEquationInstruction(
       15, SpvOpLogicalNot, {14}, return_instruction);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(15, {}), MakeDataDescriptor(7, {})));
@@ -256,13 +262,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -291,15 +295,19 @@
       14, SpvOpIAdd, {15, 16}, return_instruction);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation2 = TransformationEquationInstruction(
       19, SpvOpISub, {14, 16}, return_instruction);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(15, {}), MakeDataDescriptor(19, {})));
 
@@ -307,8 +315,10 @@
       20, SpvOpISub, {14, 15}, return_instruction);
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {})));
 
@@ -316,15 +326,19 @@
       22, SpvOpISub, {16, 14}, return_instruction);
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation5 = TransformationEquationInstruction(
       24, SpvOpSNegate, {22}, return_instruction);
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(24, {}), MakeDataDescriptor(15, {})));
 
@@ -380,13 +394,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -394,15 +406,19 @@
       14, SpvOpISub, {15, 16}, return_instruction);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation2 = TransformationEquationInstruction(
       17, SpvOpIAdd, {14, 16}, return_instruction);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(17, {}), MakeDataDescriptor(15, {})));
 
@@ -410,8 +426,10 @@
       18, SpvOpIAdd, {16, 14}, return_instruction);
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(17, {}), MakeDataDescriptor(18, {})));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
@@ -421,15 +439,19 @@
       19, SpvOpISub, {14, 15}, return_instruction);
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation5 = TransformationEquationInstruction(
       20, SpvOpSNegate, {19}, return_instruction);
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(20, {}), MakeDataDescriptor(16, {})));
 
@@ -437,8 +459,10 @@
       21, SpvOpISub, {14, 19}, return_instruction);
   ASSERT_TRUE(
       transformation6.IsApplicable(context.get(), transformation_context));
-  transformation6.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(21, {}), MakeDataDescriptor(15, {})));
 
@@ -446,15 +470,19 @@
       22, SpvOpISub, {14, 18}, return_instruction);
   ASSERT_TRUE(
       transformation7.IsApplicable(context.get(), transformation_context));
-  transformation7.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation7, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation8 = TransformationEquationInstruction(
       23, SpvOpSNegate, {22}, return_instruction);
   ASSERT_TRUE(
       transformation8.IsApplicable(context.get(), transformation_context));
-  transformation8.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation8, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(23, {}), MakeDataDescriptor(16, {})));
 
@@ -522,13 +550,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   // Too many operands.
@@ -576,7 +602,8 @@
         fresh_id, SpvOpBitcast, {operand_id}, insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   std::string expected_shader = R"(
@@ -647,13 +674,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   // Scalar floating-point type does not exist.
@@ -696,13 +721,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   // Scalar integral type does not exist.
@@ -741,13 +764,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   {
@@ -755,14 +776,16 @@
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(51, SpvOpBitcast, {20},
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   std::string expected_shader = R"(
@@ -816,13 +839,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   {
@@ -830,14 +851,16 @@
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(51, SpvOpBitcast, {20},
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   std::string expected_shader = R"(
@@ -890,13 +913,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   {
@@ -904,14 +925,16 @@
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   ASSERT_FALSE(
       TransformationEquationInstruction(51, SpvOpBitcast, {20}, insert_before)
           .IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -961,13 +984,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   {
@@ -975,14 +996,16 @@
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   ASSERT_FALSE(
       TransformationEquationInstruction(51, SpvOpBitcast, {20}, insert_before)
           .IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -1034,13 +1057,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   {
@@ -1048,17 +1069,20 @@
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(51, SpvOpBitcast, {20},
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -1113,13 +1137,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto insert_before = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   {
@@ -1127,17 +1149,20 @@
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(51, SpvOpBitcast, {20},
                                                      insert_before);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -1187,13 +1212,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -1201,15 +1224,19 @@
       522, SpvOpISub, {113, 113}, return_instruction);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation2 = TransformationEquationInstruction(
       570, SpvOpIAdd, {522, 113}, return_instruction);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1257,13 +1284,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -1271,15 +1296,19 @@
       522, SpvOpISub, {113, 113}, return_instruction);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation2 = TransformationEquationInstruction(
       570, SpvOpIAdd, {522, 113}, return_instruction);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1341,13 +1370,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -1386,59 +1413,68 @@
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(51, SpvOpConvertSToF, {10},
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(52, SpvOpConvertUToF, {16},
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(53, SpvOpConvertUToF, {11},
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(58, SpvOpConvertSToF, {18},
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(59, SpvOpConvertUToF, {19},
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(60, SpvOpConvertSToF, {20},
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationEquationInstruction transformation(61, SpvOpConvertUToF, {21},
                                                      return_instruction);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformations = R"(
                OpCapability Shader
@@ -1509,13 +1545,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::InstructionDescriptor return_instruction =
       MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
@@ -1562,13 +1596,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto return_instruction = MakeInstructionDescriptor(13, SpvOpReturn, 0);
 
   // Applicable.
@@ -1578,14 +1610,71 @@
       transformation.IsApplicable(context.get(), transformation_context));
 
   // Handles irrelevant ids.
-  fact_manager.AddFactIdIsIrrelevant(16);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(16);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
-  fact_manager.AddFactIdIsIrrelevant(15);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(15);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
 }
 
+TEST(TransformationEquationInstructionTest, HandlesDeadBlock) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %12 "main"
+               OpExecutionMode %12 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+         %30 = OpTypeVector %6 3
+         %15 = OpConstant %6 24
+         %16 = OpConstant %6 37
+         %31 = OpConstantComposite %30 %15 %16 %15
+         %33 = OpTypeBool
+         %32 = OpConstantTrue %33
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+               OpSelectionMerge %40 None
+               OpBranchConditional %32 %40 %41
+         %41 = OpLabel
+               OpBranch %40
+         %40 = 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);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(41);
+
+  TransformationEquationInstruction transformation1(
+      14, SpvOpIAdd, {15, 16},
+      MakeInstructionDescriptor(13, SpvOpSelectionMerge, 0));
+  // No synonym is created since block is dead.
+  TransformationEquationInstruction transformation2(
+      100, SpvOpISub, {14, 16}, MakeInstructionDescriptor(41, SpvOpBranch, 0));
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(15, {})));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_expand_vector_reduction_test.cpp b/test/fuzz/transformation_expand_vector_reduction_test.cpp
new file mode 100644
index 0000000..ae5c4af
--- /dev/null
+++ b/test/fuzz/transformation_expand_vector_reduction_test.cpp
@@ -0,0 +1,284 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_expand_vector_reduction.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(TransformationExpandVectorReductionTest, IsApplicable) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %9 "main"
+
+          ; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVector %2 2
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+
+          ; Constants
+          %6 = OpConstantTrue %2
+          %7 = OpConstantFalse %2
+          %8 = OpConstantComposite %3 %6 %7
+
+          ; main function
+          %9 = OpFunction %4 None %5
+         %10 = OpLabel
+         %11 = OpAny %2 %8
+         %12 = OpAll %2 %8
+               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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Tests undefined instruction.
+  auto transformation = TransformationExpandVectorReduction(13, {14, 15, 16});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests non OpAny or OpAll instruction.
+  transformation = TransformationExpandVectorReduction(10, {13, 14, 15});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests the number of fresh ids being different than the necessary.
+  transformation = TransformationExpandVectorReduction(11, {13, 14});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationExpandVectorReduction(12, {13, 14, 15, 16});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests non-fresh ids.
+  transformation = TransformationExpandVectorReduction(11, {12, 13, 14});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests duplicated fresh ids.
+  transformation = TransformationExpandVectorReduction(11, {13, 13, 14});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable transformations.
+  transformation = TransformationExpandVectorReduction(11, {13, 14, 15});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationExpandVectorReduction(12, {13, 14, 15});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationExpandVectorReductionTest, Apply) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %13 "main"
+
+          ; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVector %2 2
+          %4 = OpTypeVector %2 3
+          %5 = OpTypeVector %2 4
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+
+          ; Constants
+          %8 = OpConstantTrue %2
+          %9 = OpConstantFalse %2
+         %10 = OpConstantComposite %3 %8 %9
+         %11 = OpConstantComposite %4 %8 %9 %8
+         %12 = OpConstantComposite %5 %8 %9 %8 %9
+
+         ; main function
+         %13 = OpFunction %6 None %7
+         %14 = OpLabel
+
+         ; OpAny for 2-dimensional vector
+         %15 = OpAny %2 %10
+
+         ; OpAny for 3-dimensional vector
+         %16 = OpAny %2 %11
+
+         ; OpAny for 4-dimensional vector
+         %17 = OpAny %2 %12
+
+         ; OpAll for 2-dimensional vector
+         %18 = OpAll %2 %10
+
+         ; OpAll for 3-dimensional vector
+         %19 = OpAll %2 %11
+
+         ; OpAll for 4-dimensional vector
+         %20 = OpAll %2 %12
+               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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds OpAny synonym for 2-dimensional vector.
+  auto transformation = TransformationExpandVectorReduction(15, {21, 22, 23});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(23, {}), MakeDataDescriptor(15, {})));
+
+  // Adds OpAny synonym for 3-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(16, {24, 25, 26, 27, 28});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(28, {}), MakeDataDescriptor(16, {})));
+
+  // Adds OpAny synonym for 4-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(17, {29, 30, 31, 32, 33, 34, 35});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(35, {}), MakeDataDescriptor(17, {})));
+
+  // Adds OpAll synonym for 2-dimensional vector.
+  transformation = TransformationExpandVectorReduction(18, {36, 37, 38});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {}), MakeDataDescriptor(18, {})));
+
+  // Adds OpAll synonym for 3-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(19, {39, 40, 41, 42, 43});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(43, {}), MakeDataDescriptor(19, {})));
+
+  // Adds OpAll synonym for 4-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(20, {44, 45, 46, 47, 48, 49, 50});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {}), MakeDataDescriptor(20, {})));
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %13 "main"
+
+          ; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVector %2 2
+          %4 = OpTypeVector %2 3
+          %5 = OpTypeVector %2 4
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+
+          ; Constants
+          %8 = OpConstantTrue %2
+          %9 = OpConstantFalse %2
+         %10 = OpConstantComposite %3 %8 %9
+         %11 = OpConstantComposite %4 %8 %9 %8
+         %12 = OpConstantComposite %5 %8 %9 %8 %9
+
+         ; main function
+         %13 = OpFunction %6 None %7
+         %14 = OpLabel
+
+         ; Add OpAny synonym for 2-dimensional vector
+         %21 = OpCompositeExtract %2 %10 0
+         %22 = OpCompositeExtract %2 %10 1
+         %23 = OpLogicalOr %2 %21 %22
+         %15 = OpAny %2 %10
+
+         ; Add OpAny synonym for 3-dimensional vector
+         %24 = OpCompositeExtract %2 %11 0
+         %25 = OpCompositeExtract %2 %11 1
+         %26 = OpCompositeExtract %2 %11 2
+         %27 = OpLogicalOr %2 %24 %25
+         %28 = OpLogicalOr %2 %26 %27
+         %16 = OpAny %2 %11
+
+         ; Add OpAny synonym for 4-dimensional vector
+         %29 = OpCompositeExtract %2 %12 0
+         %30 = OpCompositeExtract %2 %12 1
+         %31 = OpCompositeExtract %2 %12 2
+         %32 = OpCompositeExtract %2 %12 3
+         %33 = OpLogicalOr %2 %29 %30
+         %34 = OpLogicalOr %2 %31 %33
+         %35 = OpLogicalOr %2 %32 %34
+         %17 = OpAny %2 %12
+
+         ; Add OpAll synonym for 2-dimensional vector
+         %36 = OpCompositeExtract %2 %10 0
+         %37 = OpCompositeExtract %2 %10 1
+         %38 = OpLogicalAnd %2 %36 %37
+         %18 = OpAll %2 %10
+
+         ; Add OpAll synonym for 3-dimensional vector
+         %39 = OpCompositeExtract %2 %11 0
+         %40 = OpCompositeExtract %2 %11 1
+         %41 = OpCompositeExtract %2 %11 2
+         %42 = OpLogicalAnd %2 %39 %40
+         %43 = OpLogicalAnd %2 %41 %42
+         %19 = OpAll %2 %11
+
+         ; Add OpAll synonym for 4-dimensional vector
+         %44 = OpCompositeExtract %2 %12 0
+         %45 = OpCompositeExtract %2 %12 1
+         %46 = OpCompositeExtract %2 %12 2
+         %47 = OpCompositeExtract %2 %12 3
+         %48 = OpLogicalAnd %2 %44 %45
+         %49 = OpLogicalAnd %2 %46 %48
+         %50 = OpLogicalAnd %2 %47 %49
+         %20 = OpAll %2 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_flatten_conditional_branch_test.cpp b/test/fuzz/transformation_flatten_conditional_branch_test.cpp
new file mode 100644
index 0000000..0aaf20e
--- /dev/null
+++ b/test/fuzz/transformation_flatten_conditional_branch_test.cpp
@@ -0,0 +1,2046 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_flatten_conditional_branch.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/counter_overflow_id_source.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 {
+
+protobufs::SideEffectWrapperInfo MakeSideEffectWrapperInfo(
+    const protobufs::InstructionDescriptor& instruction,
+    uint32_t merge_block_id, uint32_t execute_block_id,
+    uint32_t actual_result_id, uint32_t alternative_block_id,
+    uint32_t placeholder_result_id, uint32_t value_to_copy_id) {
+  protobufs::SideEffectWrapperInfo result;
+  *result.mutable_instruction() = instruction;
+  result.set_merge_block_id(merge_block_id);
+  result.set_execute_block_id(execute_block_id);
+  result.set_actual_result_id(actual_result_id);
+  result.set_alternative_block_id(alternative_block_id);
+  result.set_placeholder_result_id(placeholder_result_id);
+  result.set_value_to_copy_id(value_to_copy_id);
+  return result;
+}
+
+protobufs::SideEffectWrapperInfo MakeSideEffectWrapperInfo(
+    const protobufs::InstructionDescriptor& instruction,
+    uint32_t merge_block_id, uint32_t execute_block_id) {
+  return MakeSideEffectWrapperInfo(instruction, merge_block_id,
+                                   execute_block_id, 0, 0, 0, 0);
+}
+
+TEST(TransformationFlattenConditionalBranchTest, Inapplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 0
+          %9 = OpTypeBool
+         %10 = OpConstantTrue %9
+         %11 = OpTypePointer Function %6
+         %12 = OpTypePointer Workgroup %6
+          %3 = OpVariable %12 Workgroup
+         %13 = OpConstant %6 2
+          %2 = OpFunction %4 None %5
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpSwitch %13 %17 2 %18
+         %17 = OpLabel
+               OpBranch %16
+         %18 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %19 %16 None
+               OpBranchConditional %10 %16 %19
+         %19 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %10 %21 %20
+         %21 = OpLabel
+               OpReturn
+         %20 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %10 %23 %22
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %10 %25 %24
+         %25 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %10 %26 %27
+         %27 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpLoopMerge %29 %28 None
+               OpBranchConditional %10 %28 %29
+         %29 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpSelectionMerge %30 None
+               OpBranchConditional %10 %30 %31
+         %31 = OpLabel
+               OpBranch %32
+         %32 = OpLabel
+         %33 = OpAtomicLoad %6 %3 %8 %8
+               OpBranch %30
+         %30 = OpLabel
+               OpSelectionMerge %34 None
+               OpBranchConditional %10 %35 %34
+         %35 = OpLabel
+               OpMemoryBarrier %8 %8
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %40 %39 None
+               OpBranchConditional %10 %36 %40
+         %36 = OpLabel
+               OpSelectionMerge %38 None
+               OpBranchConditional %10 %37 %38
+         %37 = OpLabel
+               OpBranch %40
+         %38 = OpLabel
+               OpBranch %39
+         %39 = OpLabel
+               OpBranch %34
+         %40 = 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);
+  // Block %15 does not end with OpBranchConditional.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(15, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %17 is not a selection header.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(17, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %16 is a loop header, not a selection header.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %19 and the corresponding merge block do not describe a single-entry,
+  // single-exit region, because there is a return instruction in %21.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(19, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %20 is the header of a construct containing an inner selection
+  // construct.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(20, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %22 is the header of a construct containing an inner loop.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(22, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %30 is the header of a construct containing a barrier instruction.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(30, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %33 is not a block.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(33, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %36 and the corresponding merge block do not describe a single-entry,
+  // single-exit region, because block %37 breaks out of the outer loop.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(36, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, Simple) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeBool
+          %4 = OpConstantTrue %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %2 = OpFunction %5 None %6
+          %7 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %4 %9 %10
+         %10 = OpLabel
+         %26 = OpPhi %3 %4 %7
+               OpBranch %8
+          %9 = OpLabel
+         %27 = OpPhi %3 %4 %7
+         %11 = OpCopyObject %3 %4
+               OpBranch %8
+          %8 = OpLabel
+         %12 = OpPhi %3 %11 %9 %4 %10
+         %23 = OpPhi %3 %4 %9 %4 %10
+               OpBranch %13
+         %13 = OpLabel
+         %14 = OpCopyObject %3 %4
+               OpSelectionMerge %15 None
+               OpBranchConditional %4 %16 %17
+         %16 = OpLabel
+         %28 = OpPhi %3 %4 %13
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %19
+         %17 = OpLabel
+         %29 = OpPhi %3 %4 %13
+         %20 = OpCopyObject %3 %4
+               OpBranch %19
+         %19 = OpLabel
+         %21 = OpPhi %3 %4 %18 %20 %17
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %4 %22 %22
+         %22 = OpLabel
+         %30 = OpPhi %3 %4 %15
+               OpSelectionMerge %25 None
+               OpBranchConditional %4 %24 %24
+         %24 = OpLabel
+               OpBranch %25
+         %25 = 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);
+  auto transformation1 =
+      TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+
+  auto transformation2 =
+      TransformationFlattenConditionalBranch(13, false, 0, 0, 0, {});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+
+  auto transformation3 =
+      TransformationFlattenConditionalBranch(15, true, 0, 0, 0, {});
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+
+  auto transformation4 =
+      TransformationFlattenConditionalBranch(22, false, 0, 0, 0, {});
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeBool
+          %4 = OpConstantTrue %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %2 = OpFunction %5 None %6
+          %7 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+         %27 = OpPhi %3 %4 %7
+         %11 = OpCopyObject %3 %4
+               OpBranch %10
+         %10 = OpLabel
+         %26 = OpPhi %3 %4 %9
+               OpBranch %8
+          %8 = OpLabel
+         %12 = OpSelect %3 %4 %11 %4
+         %23 = OpSelect %3 %4 %4 %4
+               OpBranch %13
+         %13 = OpLabel
+         %14 = OpCopyObject %3 %4
+               OpBranch %17
+         %17 = OpLabel
+         %29 = OpPhi %3 %4 %13
+         %20 = OpCopyObject %3 %4
+               OpBranch %16
+         %16 = OpLabel
+         %28 = OpPhi %3 %4 %17
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+         %21 = OpSelect %3 %4 %4 %20
+               OpBranch %15
+         %15 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+         %30 = OpPhi %3 %4 %15
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %25
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, LoadStoreFunctionCall) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %9 = OpTypeVoid
+         %10 = OpTypeFunction %9
+         %11 = OpTypeInt 32 1
+         %12 = OpTypeVector %11 4
+         %13 = OpTypeFunction %11
+         %70 = OpConstant %11 0
+         %14 = OpConstant %11 1
+         %15 = OpTypeFloat 32
+         %16 = OpTypeVector %15 2
+         %17 = OpConstant %15 1
+         %18 = OpConstantComposite %16 %17 %17
+         %19 = OpTypeBool
+         %20 = OpConstantTrue %19
+         %21 = OpTypePointer Function %11
+         %22 = OpTypeSampler
+         %23 = OpTypeImage %9 2D 2 0 0 1 Unknown
+         %24 = OpTypeSampledImage %23
+         %25 = OpTypePointer Function %23
+         %26 = OpTypePointer Function %22
+         %27 = OpTypeInt 32 0
+         %28 = OpConstant %27 2
+         %29 = OpTypeArray %11 %28
+         %30 = OpTypePointer Function %29
+          %2 = OpFunction %9 None %10
+         %31 = OpLabel
+          %4 = OpVariable %21 Function
+          %5 = OpVariable %30 Function
+         %32 = OpVariable %25 Function
+         %33 = OpVariable %26 Function
+         %34 = OpLoad %23 %32
+         %35 = OpLoad %22 %33
+               OpSelectionMerge %36 None
+               OpBranchConditional %20 %37 %36
+         %37 = OpLabel
+          %6 = OpLoad %11 %4
+          %7 = OpIAdd %11 %6 %14
+               OpStore %4 %7
+               OpBranch %36
+         %36 = OpLabel
+         %42 = OpPhi %11 %14 %37 %14 %31
+               OpSelectionMerge %43 None
+               OpBranchConditional %20 %44 %45
+         %44 = OpLabel
+          %8 = OpFunctionCall %11 %3
+               OpStore %4 %8
+               OpBranch %46
+         %45 = OpLabel
+         %47 = OpAccessChain %21 %5 %14
+               OpStore %47 %14
+               OpBranch %46
+         %46 = OpLabel
+               OpStore %4 %14
+               OpBranch %43
+         %43 = OpLabel
+               OpStore %4 %14
+               OpSelectionMerge %48 None
+               OpBranchConditional %20 %49 %48
+         %49 = OpLabel
+               OpBranch %48
+         %48 = OpLabel
+               OpSelectionMerge %50 None
+               OpBranchConditional %20 %51 %50
+         %51 = OpLabel
+         %52 = OpSampledImage %24 %34 %35
+         %53 = OpLoad %11 %4
+         %54 = OpImageSampleImplicitLod %12 %52 %18
+               OpBranch %50
+         %50 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %3 = OpFunction %11 None %13
+         %55 = OpLabel
+               OpReturnValue %14
+               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
+  // The following checks lead to assertion failures, since some entries
+  // requiring fresh ids are not present in the map, and the transformation
+  // context does not have a source overflow ids.
+
+  ASSERT_DEATH(TransformationFlattenConditionalBranch(31, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context),
+               "Bad attempt to query whether overflow ids are available.");
+
+  ASSERT_DEATH(TransformationFlattenConditionalBranch(
+                   31, true, 0, 0, 0,
+                   {{MakeSideEffectWrapperInfo(
+                       MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101,
+                       102, 103, 104, 14)}})
+                   .IsApplicable(context.get(), transformation_context),
+               "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  // The map maps from an instruction to a list with not enough fresh ids.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(
+                   31, true, 0, 0, 0,
+                   {{MakeSideEffectWrapperInfo(
+                       MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 101,
+                       102, 103, 0, 0)}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Not all fresh ids given are distinct.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(
+                   31, true, 0, 0, 0,
+                   {{MakeSideEffectWrapperInfo(
+                       MakeInstructionDescriptor(6, SpvOpLoad, 0), 100, 100,
+                       102, 103, 104, 0)}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %48 heads a construct containing an OpSampledImage instruction.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(
+                   48, true, 0, 0, 0,
+                   {{MakeSideEffectWrapperInfo(
+                       MakeInstructionDescriptor(53, SpvOpLoad, 0), 100, 101,
+                       102, 103, 104, 0)}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %0 is not a valid id.
+  ASSERT_FALSE(
+      TransformationFlattenConditionalBranch(
+          31, true, 0, 0, 0,
+          {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
+                                     104, 100, 101, 102, 103, 0),
+           MakeSideEffectWrapperInfo(
+               MakeInstructionDescriptor(6, SpvOpStore, 0), 106, 105)})
+          .IsApplicable(context.get(), transformation_context));
+
+  // %17 is a float constant, while %6 has int type.
+  ASSERT_FALSE(
+      TransformationFlattenConditionalBranch(
+          31, true, 0, 0, 0,
+          {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
+                                     104, 100, 101, 102, 103, 17),
+           MakeSideEffectWrapperInfo(
+               MakeInstructionDescriptor(6, SpvOpStore, 0), 106, 105)})
+          .IsApplicable(context.get(), transformation_context));
+
+  auto transformation1 = TransformationFlattenConditionalBranch(
+      31, true, 0, 0, 0,
+      {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpLoad, 0),
+                                 104, 100, 101, 102, 103, 70),
+       MakeSideEffectWrapperInfo(MakeInstructionDescriptor(6, SpvOpStore, 0),
+                                 106, 105)});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+
+  // Check that the placeholder id was marked as irrelevant.
+  ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(103));
+
+  // Make a new transformation context with a source of overflow ids.
+  auto overflow_ids_unique_ptr = MakeUnique<CounterOverflowIdSource>(1000);
+  auto overflow_ids_ptr = overflow_ids_unique_ptr.get();
+  TransformationContext new_transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options,
+      std::move(overflow_ids_unique_ptr));
+
+  auto transformation2 = TransformationFlattenConditionalBranch(
+      36, false, 0, 0, 0,
+      {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(8, SpvOpStore, 0),
+                                 114, 113)});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), new_transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &new_transformation_context,
+                        overflow_ids_ptr->GetIssuedOverflowIds());
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %9 = OpTypeVoid
+         %10 = OpTypeFunction %9
+         %11 = OpTypeInt 32 1
+         %12 = OpTypeVector %11 4
+         %13 = OpTypeFunction %11
+         %70 = OpConstant %11 0
+         %14 = OpConstant %11 1
+         %15 = OpTypeFloat 32
+         %16 = OpTypeVector %15 2
+         %17 = OpConstant %15 1
+         %18 = OpConstantComposite %16 %17 %17
+         %19 = OpTypeBool
+         %20 = OpConstantTrue %19
+         %21 = OpTypePointer Function %11
+         %22 = OpTypeSampler
+         %23 = OpTypeImage %9 2D 2 0 0 1 Unknown
+         %24 = OpTypeSampledImage %23
+         %25 = OpTypePointer Function %23
+         %26 = OpTypePointer Function %22
+         %27 = OpTypeInt 32 0
+         %28 = OpConstant %27 2
+         %29 = OpTypeArray %11 %28
+         %30 = OpTypePointer Function %29
+          %2 = OpFunction %9 None %10
+         %31 = OpLabel
+          %4 = OpVariable %21 Function
+          %5 = OpVariable %30 Function
+         %32 = OpVariable %25 Function
+         %33 = OpVariable %26 Function
+         %34 = OpLoad %23 %32
+         %35 = OpLoad %22 %33
+               OpBranch %37
+         %37 = OpLabel
+               OpSelectionMerge %104 None
+               OpBranchConditional %20 %100 %102
+        %100 = OpLabel
+        %101 = OpLoad %11 %4
+               OpBranch %104
+        %102 = OpLabel
+        %103 = OpCopyObject %11 %70
+               OpBranch %104
+        %104 = OpLabel
+          %6 = OpPhi %11 %101 %100 %103 %102
+          %7 = OpIAdd %11 %6 %14
+               OpSelectionMerge %106 None
+               OpBranchConditional %20 %105 %106
+        %105 = OpLabel
+               OpStore %4 %7
+               OpBranch %106
+        %106 = OpLabel
+               OpBranch %36
+         %36 = OpLabel
+         %42 = OpSelect %11 %20 %14 %14
+               OpBranch %45
+         %45 = OpLabel
+         %47 = OpAccessChain %21 %5 %14
+               OpSelectionMerge %1005 None
+               OpBranchConditional %20 %1005 %1006
+       %1006 = OpLabel
+               OpStore %47 %14
+               OpBranch %1005
+       %1005 = OpLabel
+               OpBranch %44
+         %44 = OpLabel
+               OpSelectionMerge %1000 None
+               OpBranchConditional %20 %1001 %1003
+       %1001 = OpLabel
+       %1002 = OpFunctionCall %11 %3
+               OpBranch %1000
+       %1003 = OpLabel
+       %1004 = OpCopyObject %11 %70
+               OpBranch %1000
+       %1000 = OpLabel
+          %8 = OpPhi %11 %1002 %1001 %1004 %1003
+               OpSelectionMerge %114 None
+               OpBranchConditional %20 %113 %114
+        %113 = OpLabel
+               OpStore %4 %8
+               OpBranch %114
+        %114 = OpLabel
+               OpBranch %46
+         %46 = OpLabel
+               OpStore %4 %14
+               OpBranch %43
+         %43 = OpLabel
+               OpStore %4 %14
+               OpSelectionMerge %48 None
+               OpBranchConditional %20 %49 %48
+         %49 = OpLabel
+               OpBranch %48
+         %48 = OpLabel
+               OpSelectionMerge %50 None
+               OpBranchConditional %20 %51 %50
+         %51 = OpLabel
+         %52 = OpSampledImage %24 %34 %35
+         %53 = OpLoad %11 %4
+         %54 = OpImageSampleImplicitLod %12 %52 %18
+               OpBranch %50
+         %50 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %3 = OpFunction %11 None %13
+         %55 = OpLabel
+               OpReturnValue %14
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}  // namespace
+
+TEST(TransformationFlattenConditionalBranchTest, EdgeCases) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %5 %9 %8
+          %9 = OpLabel
+         %10 = OpFunctionCall %3 %11
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %5 %13 %12
+         %13 = OpLabel
+         %14 = OpFunctionCall %3 %11
+         %15 = OpCopyObject %3 %14
+               OpBranch %12
+         %12 = OpLabel
+               OpReturn
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %5 %18 %17
+         %18 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %3 None %6
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpSelectionMerge %25 None
+               OpBranchConditional %5 %21 %22
+         %21 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %5 %24 %23
+         %23 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %25
+         %25 = 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
+  // The selection construct headed by %7 requires fresh ids because it contains
+  // a function call. This causes an assertion failure because transformation
+  // context does not have a source of overflow ids.
+  ASSERT_DEATH(TransformationFlattenConditionalBranch(7, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context),
+               "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  auto transformation1 = TransformationFlattenConditionalBranch(
+      7, true, 0, 0, 0,
+      {{MakeSideEffectWrapperInfo(
+          MakeInstructionDescriptor(10, SpvOpFunctionCall, 0), 100, 101)}});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+
+  // The selection construct headed by %8 cannot be flattened because it
+  // contains a function call returning void, whose result id is used.
+  ASSERT_FALSE(
+      TransformationFlattenConditionalBranch(
+          7, true, 0, 0, 0,
+          {{MakeSideEffectWrapperInfo(
+              MakeInstructionDescriptor(14, SpvOpFunctionCall, 0), 102, 103)}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Block %16 is unreachable.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(16, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation2 =
+      TransformationFlattenConditionalBranch(20, false, 0, 0, 0, {});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, 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 %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpSelectionMerge %100 None
+               OpBranchConditional %5 %101 %100
+        %101 = OpLabel
+         %10 = OpFunctionCall %3 %11
+               OpBranch %100
+        %100 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %5 %13 %12
+         %13 = OpLabel
+         %14 = OpFunctionCall %3 %11
+         %15 = OpCopyObject %3 %14
+               OpBranch %12
+         %12 = OpLabel
+               OpReturn
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %5 %18 %17
+         %18 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %3 None %6
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %5 %24 %23
+         %23 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %25
+         %25 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, PhiToSelect1) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %5 %9 %8
+          %9 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpPhi %4 %5 %9 %10 %7
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(7, true, 0, 0, 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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpSelect %4 %5 %5 %10
+               OpReturn
+               OpFunctionEnd
+)";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, PhiToSelect2) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %5 %9 %8
+          %9 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpPhi %4 %10 %7 %5 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(7, true, 0, 0, 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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpSelect %4 %5 %5 %10
+               OpReturn
+               OpFunctionEnd
+)";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, PhiToSelect3) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %5 %9 %12
+          %9 = OpLabel
+               OpBranch %8
+         %12 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpPhi %4 %10 %12 %5 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(7, true, 0, 0, 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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpSelect %4 %5 %5 %10
+               OpReturn
+               OpFunctionEnd
+)";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, PhiToSelect4) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpSelectionMerge %8 None
+               OpBranchConditional %5 %9 %12
+          %9 = OpLabel
+               OpBranch %8
+         %12 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpPhi %4 %5 %9 %10 %12
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(7, true, 0, 0, 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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %12
+         %12 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %11 = OpSelect %4 %5 %5 %10
+               OpReturn
+               OpFunctionEnd
+)";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, PhiToSelect5) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+        %100 = OpTypePointer Function %4
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+        %101 = OpVariable %100 Function
+        %102 = OpVariable %100 Function
+               OpSelectionMerge %470 None
+               OpBranchConditional %5 %454 %462
+        %454 = OpLabel
+        %522 = OpLoad %4 %101
+               OpBranch %470
+        %462 = OpLabel
+        %466 = OpLoad %4 %102
+               OpBranch %470
+        %470 = OpLabel
+        %534 = OpPhi %4 %522 %454 %466 %462
+               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);
+
+  auto transformation = TransformationFlattenConditionalBranch(
+      7, true, 0, 0, 0,
+      {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(522, SpvOpLoad, 0),
+                                 200, 201, 202, 203, 204, 5),
+       MakeSideEffectWrapperInfo(MakeInstructionDescriptor(466, SpvOpLoad, 0),
+                                 300, 301, 302, 303, 304, 5)});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeBool
+          %5 = OpConstantTrue %4
+         %10 = OpConstantFalse %4
+          %6 = OpTypeFunction %3
+        %100 = OpTypePointer Function %4
+          %2 = OpFunction %3 None %6
+          %7 = OpLabel
+        %101 = OpVariable %100 Function
+        %102 = OpVariable %100 Function
+               OpBranch %454
+        %454 = OpLabel
+               OpSelectionMerge %200 None
+               OpBranchConditional %5 %201 %203
+        %201 = OpLabel
+        %202 = OpLoad %4 %101
+               OpBranch %200
+        %203 = OpLabel
+        %204 = OpCopyObject %4 %5
+               OpBranch %200
+        %200 = OpLabel
+        %522 = OpPhi %4 %202 %201 %204 %203
+               OpBranch %462
+        %462 = OpLabel
+               OpSelectionMerge %300 None
+               OpBranchConditional %5 %303 %301
+        %301 = OpLabel
+        %302 = OpLoad %4 %102
+               OpBranch %300
+        %303 = OpLabel
+        %304 = OpCopyObject %4 %5
+               OpBranch %300
+        %300 = OpLabel
+        %466 = OpPhi %4 %302 %301 %304 %303
+               OpBranch %470
+        %470 = OpLabel
+        %534 = OpSelect %4 %5 %522 %466
+               OpReturn
+               OpFunctionEnd
+)";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest,
+     LoadFromBufferBlockDecoratedStruct) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpMemberDecorate %11 0 Offset 0
+               OpDecorate %11 BufferBlock
+               OpDecorate %13 DescriptorSet 0
+               OpDecorate %13 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeStruct %10
+         %12 = OpTypePointer Uniform %11
+         %13 = OpVariable %12 Uniform
+         %21 = OpUndef %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+         %20 = OpLoad %11 %13
+               OpBranch %9
+          %9 = 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);
+
+  auto transformation = TransformationFlattenConditionalBranch(
+      5, true, 0, 0, 0,
+      {MakeSideEffectWrapperInfo(MakeInstructionDescriptor(20, SpvOpLoad, 0),
+                                 100, 101, 102, 103, 104, 21)});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, InapplicableSampledImageLoad) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %12 %96
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %12 BuiltIn FragCoord
+               OpDecorate %91 DescriptorSet 0
+               OpDecorate %91 Binding 0
+               OpDecorate %96 Location 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeVector %6 2
+         %10 = OpTypeVector %6 4
+         %11 = OpTypePointer Input %10
+         %12 = OpVariable %11 Input
+         %21 = OpConstant %6 2
+         %24 = OpTypeInt 32 1
+         %33 = OpTypeBool
+         %35 = OpConstantTrue %33
+         %88 = OpTypeImage %6 2D 0 0 0 1 Unknown
+         %89 = OpTypeSampledImage %88
+         %90 = OpTypePointer UniformConstant %89
+         %91 = OpVariable %90 UniformConstant
+         %95 = OpTypePointer Output %10
+         %96 = OpVariable %95 Output
+        %200 = OpUndef %89
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpSelectionMerge %38 None
+               OpBranchConditional %35 %32 %37
+         %32 = OpLabel
+         %40 = OpLoad %89 %91
+               OpBranch %38
+         %37 = OpLabel
+               OpBranch %38
+         %38 = 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);
+
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(
+                   28, true, 0, 0, 0,
+                   {MakeSideEffectWrapperInfo(
+                       MakeInstructionDescriptor(40, SpvOpLoad, 0), 100, 101,
+                       102, 103, 104, 200)})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationFlattenConditionalBranchTest,
+     InapplicablePhiToSelectVector) {
+  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
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %12 = OpUndef %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %11 %12 %8 %12 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationFlattenConditionalBranchTest,
+     InapplicablePhiToSelectVector2) {
+  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
+         %30 = OpTypeVector %6 3
+         %31 = OpTypeVector %6 2
+          %7 = OpConstantTrue %6
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %40 = OpTypeFloat 32
+         %41 = OpTypeVector %40 4
+         %12 = OpUndef %11
+         %60 = OpUndef %41
+	 %61 = OpConstantComposite %31 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %11 %12 %8 %12 %9
+         %22 = OpPhi %11 %12 %8 %12 %9
+         %23 = OpPhi %41 %60 %8 %60 %9
+         %24 = OpPhi %31 %61 %8 %61 %9
+         %25 = OpPhi %41 %60 %8 %60 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(5, true, 101, 102, 103, {});
+
+  // bvec4 is not present in the module.
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+}
+
+TEST(TransformationFlattenConditionalBranchTest,
+     InapplicablePhiToSelectMatrix) {
+  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
+         %10 = OpTypeFloat 32
+         %30 = OpTypeVector %10 3
+         %11 = OpTypeMatrix %30 3
+         %12 = OpUndef %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %11 %12 %8 %12 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectVector) {
+  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
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %12 = OpUndef %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %11 %12 %8 %12 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(5, true, 0, 0, 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 expected_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
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %12 = OpUndef %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpSelect %11 %7 %12 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectVector2) {
+  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
+         %30 = OpTypeVector %6 3
+         %31 = OpTypeVector %6 2
+         %32 = OpTypeVector %6 4
+          %7 = OpConstantTrue %6
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %40 = OpTypeFloat 32
+         %41 = OpTypeVector %40 4
+         %12 = OpUndef %11
+         %60 = OpUndef %41
+	 %61 = OpConstantComposite %31 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %11 %12 %8 %12 %9
+         %22 = OpPhi %11 %12 %8 %12 %9
+         %23 = OpPhi %41 %60 %8 %60 %9
+         %24 = OpPhi %31 %61 %8 %61 %9
+         %25 = OpPhi %41 %60 %8 %60 %9
+               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);
+
+  // No id for the 2D vector case is provided.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 0, 102, 103, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // No id for the 3D vector case is provided.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 0, 103, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // No id for the 4D vector case is provided.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 102, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %10 is not fresh
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 10, 102, 103, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %10 is not fresh
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 10, 103, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %10 is not fresh
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 101, 102, 10, {})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Duplicate "fresh" ids used for boolean vector constructors
+  ASSERT_FALSE(
+      TransformationFlattenConditionalBranch(5, true, 101, 102, 102, {})
+          .IsApplicable(context.get(), transformation_context));
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(5, true, 101, 102, 103, {});
+
+  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 expected_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
+         %30 = OpTypeVector %6 3
+         %31 = OpTypeVector %6 2
+         %32 = OpTypeVector %6 4
+          %7 = OpConstantTrue %6
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %40 = OpTypeFloat 32
+         %41 = OpTypeVector %40 4
+         %12 = OpUndef %11
+         %60 = OpUndef %41
+	 %61 = OpConstantComposite %31 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+        %103 = OpCompositeConstruct %32 %7 %7 %7 %7
+        %102 = OpCompositeConstruct %30 %7 %7 %7
+        %101 = OpCompositeConstruct %31 %7 %7
+         %21 = OpSelect %11 %102 %12 %12
+         %22 = OpSelect %11 %102 %12 %12
+         %23 = OpSelect %41 %103 %60 %60
+         %24 = OpSelect %31 %101 %61 %61
+         %25 = OpSelect %41 %103 %60 %60
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectVector3) {
+  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
+         %30 = OpTypeVector %6 3
+         %31 = OpTypeVector %6 2
+         %32 = OpTypeVector %6 4
+          %7 = OpConstantTrue %6
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %40 = OpTypeFloat 32
+         %41 = OpTypeVector %40 4
+         %12 = OpUndef %11
+         %60 = OpUndef %41
+	 %61 = OpConstantComposite %31 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %11 %12 %8 %12 %9
+         %22 = OpPhi %11 %12 %8 %12 %9
+         %23 = OpPhi %41 %60 %8 %60 %9
+         %24 = OpPhi %31 %61 %8 %61 %9
+         %25 = OpPhi %41 %60 %8 %60 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(5, true, 101, 0, 103, {});
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Check that the in operands of any OpSelect instructions all have the
+  // appropriate operand type.
+  context->module()->ForEachInst([](opt::Instruction* inst) {
+    if (inst->opcode() == SpvOpSelect) {
+      ASSERT_EQ(SPV_OPERAND_TYPE_ID, inst->GetInOperand(0).type);
+      ASSERT_EQ(SPV_OPERAND_TYPE_ID, inst->GetInOperand(1).type);
+      ASSERT_EQ(SPV_OPERAND_TYPE_ID, inst->GetInOperand(2).type);
+    }
+  });
+
+  std::string expected_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
+         %30 = OpTypeVector %6 3
+         %31 = OpTypeVector %6 2
+         %32 = OpTypeVector %6 4
+          %7 = OpConstantTrue %6
+         %10 = OpTypeInt 32 1
+         %11 = OpTypeVector %10 3
+         %40 = OpTypeFloat 32
+         %41 = OpTypeVector %40 4
+         %12 = OpUndef %11
+         %60 = OpUndef %41
+	 %61 = OpConstantComposite %31 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+        %103 = OpCompositeConstruct %32 %7 %7 %7 %7
+        %101 = OpCompositeConstruct %31 %7 %7
+         %21 = OpSelect %11 %7 %12 %12
+         %22 = OpSelect %11 %7 %12 %12
+         %23 = OpSelect %41 %103 %60 %60
+         %24 = OpSelect %31 %101 %61 %61
+         %25 = OpSelect %41 %103 %60 %60
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest, ApplicablePhiToSelectMatrix) {
+  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
+         %10 = OpTypeFloat 32
+         %30 = OpTypeVector %10 3
+         %11 = OpTypeMatrix %30 3
+         %12 = OpUndef %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %20
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpPhi %11 %12 %8 %12 %9
+               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);
+
+  auto transformation =
+      TransformationFlattenConditionalBranch(5, true, 0, 0, 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 expected_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
+         %10 = OpTypeFloat 32
+         %30 = OpTypeVector %10 3
+         %11 = OpTypeMatrix %30 3
+         %12 = OpUndef %11
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %21 = OpSelect %11 %7 %12 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest,
+     InapplicableConditionIsIrrelevant) {
+  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
+         %10 = OpTypeInt 32 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %9
+          %9 = 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);
+
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(7);
+
+  // Inapplicable because the branch condition, %7, is irrelevant.
+  ASSERT_FALSE(TransformationFlattenConditionalBranch(5, true, 0, 0, 0, {})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationFlattenConditionalBranchTest,
+     OpPhiWhenTrueBranchIsConvergenceBlock) {
+  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
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %9 %8
+          %8 = OpLabel
+         %10 = OpCopyObject %6 %7
+               OpBranch %9
+          %9 = OpLabel
+         %11 = OpPhi %6 %10 %8 %7 %5
+         %12 = OpPhi %6 %7 %5 %10 %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));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  TransformationFlattenConditionalBranch transformation(5, true, 0, 0, 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 expected = 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
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %10 = OpCopyObject %6 %7
+               OpBranch %9
+          %9 = OpLabel
+         %11 = OpSelect %6 %7 %7 %10
+         %12 = OpSelect %6 %7 %7 %10
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+TEST(TransformationFlattenConditionalBranchTest,
+     OpPhiWhenFalseBranchIsConvergenceBlock) {
+  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
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+         %10 = OpCopyObject %6 %7
+               OpBranch %9
+          %9 = OpLabel
+         %11 = OpPhi %6 %10 %8 %7 %5
+         %12 = OpPhi %6 %7 %5 %10 %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));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+
+  TransformationFlattenConditionalBranch transformation(5, true, 0, 0, 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 expected = 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
+          %6 = OpTypeBool
+          %7 = OpConstantTrue %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %8
+          %8 = OpLabel
+         %10 = OpCopyObject %6 %7
+               OpBranch %9
+          %9 = OpLabel
+         %11 = OpSelect %6 %7 %10 %7
+         %12 = OpSelect %6 %7 %10 %7
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, expected, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_function_call_test.cpp b/test/fuzz/transformation_function_call_test.cpp
index d7305f8..b27b3ca 100644
--- a/test/fuzz/transformation_function_call_test.cpp
+++ b/test/fuzz/transformation_function_call_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_function_call.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -131,13 +134,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactBlockIsDead(59);
   transformation_context.GetFactManager()->AddFactBlockIsDead(11);
   transformation_context.GetFactManager()->AddFactBlockIsDead(18);
@@ -257,8 +258,10 @@
         100, 21, {71, 72}, MakeInstructionDescriptor(59, SpvOpBranch, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     // Livesafe called from original live block: fine
@@ -266,8 +269,10 @@
         101, 21, {71, 72}, MakeInstructionDescriptor(98, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     // Livesafe called from livesafe function: fine
@@ -275,8 +280,10 @@
         102, 200, {19, 20}, MakeInstructionDescriptor(36, SpvOpLoad, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     // Dead called from dead block in injected function: fine
@@ -284,8 +291,10 @@
         103, 10, {23}, MakeInstructionDescriptor(45, SpvOpLoad, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     // Non-livesafe called from dead block in livesafe function: OK
@@ -293,8 +302,10 @@
         104, 10, {201}, MakeInstructionDescriptor(205, SpvOpBranch, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     // Livesafe called from dead block with non-arbitrary parameter
@@ -302,8 +313,10 @@
         105, 21, {62, 65}, MakeInstructionDescriptor(59, SpvOpBranch, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
@@ -444,13 +457,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactBlockIsDead(11);
 
   // 4 is an entry point, so it is not legal for it to be the target of a call.
diff --git a/test/fuzz/transformation_inline_function_test.cpp b/test/fuzz/transformation_inline_function_test.cpp
new file mode 100644
index 0000000..4cd465f
--- /dev/null
+++ b/test/fuzz/transformation_inline_function_test.cpp
@@ -0,0 +1,1116 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_inline_function.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/counter_overflow_id_source.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(TransformationInlineFunctionTest, IsApplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %52 "main"
+               OpExecutionMode %52 OriginUpperLeft
+               OpName %56 "function_with_void_return"
+
+; Types
+          %2 = OpTypeBool
+          %3 = OpTypeFloat 32
+          %4 = OpTypeVector %3 4
+          %5 = OpTypePointer Function %4
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFunction %3 %5 %5
+
+; Constant scalars
+          %9 = OpConstant %3 1
+         %10 = OpConstant %3 2
+         %11 = OpConstant %3 3
+         %12 = OpConstant %3 4
+         %13 = OpConstant %3 5
+         %14 = OpConstant %3 6
+         %15 = OpConstant %3 7
+         %16 = OpConstant %3 8
+         %17 = OpConstantTrue %2
+
+; Constant vectors
+         %18 = OpConstantComposite %4 %9 %10 %11 %12
+         %19 = OpConstantComposite %4 %13 %14 %15 %16
+
+; function with void return
+         %20 = OpFunction %6 None %7
+         %21 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+; function with early return
+         %22 = OpFunction %6 None %7
+         %23 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %17 %24 %25
+         %24 = OpLabel
+               OpReturn
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+; function containing an OpKill instruction
+         %27 = OpFunction %6 None %7
+         %28 = OpLabel
+               OpKill
+               OpFunctionEnd
+
+; function containing an OpUnreachable instruction
+         %29 = OpFunction %6 None %7
+         %30 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+
+; dot product function
+         %31 = OpFunction %3 None %8
+         %32 = OpFunctionParameter %5
+         %33 = OpFunctionParameter %5
+         %34 = OpLabel
+         %35 = OpLoad %4 %32
+         %36 = OpLoad %4 %33
+         %37 = OpCompositeExtract %3 %35 0
+         %38 = OpCompositeExtract %3 %36 0
+         %39 = OpFMul %3 %37 %38
+         %40 = OpCompositeExtract %3 %35 1
+         %41 = OpCompositeExtract %3 %36 1
+         %42 = OpFMul %3 %40 %41
+         %43 = OpCompositeExtract %3 %35 2
+         %44 = OpCompositeExtract %3 %36 2
+         %45 = OpFMul %3 %43 %44
+         %46 = OpCompositeExtract %3 %35 3
+         %47 = OpCompositeExtract %3 %36 3
+         %48 = OpFMul %3 %46 %47
+         %49 = OpFAdd %3 %39 %42
+         %50 = OpFAdd %3 %45 %49
+         %51 = OpFAdd %3 %48 %50
+               OpReturnValue %51
+               OpFunctionEnd
+
+; main function
+         %52 = OpFunction %6 None %7
+         %53 = OpLabel
+         %54 = OpVariable %5 Function
+         %55 = OpVariable %5 Function
+         %56 = OpFunctionCall %6 %20 ; function with void return
+               OpBranch %57
+         %57 = OpLabel
+         %59 = OpFunctionCall %6 %22 ; function with early return
+               OpBranch %60
+         %60 = OpLabel
+         %61 = OpFunctionCall %6 %27 ; function containing OpKill
+               OpBranch %62
+         %62 = OpLabel
+         %63 = OpFunctionCall %6 %29 ; function containing OpUnreachable
+               OpBranch %64
+         %64 = OpLabel
+               OpStore %54 %18
+               OpStore %55 %19
+         %65 = OpFunctionCall %3 %31 %54 %55 ; dot product function
+               OpBranch %66
+         %66 = 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);
+  // Tests undefined OpFunctionCall instruction.
+  auto transformation = TransformationInlineFunction(67, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests false OpFunctionCall instruction.
+  transformation = TransformationInlineFunction(42, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests use of called function with void return.
+  transformation = TransformationInlineFunction(56, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests called function having an early return.
+  transformation =
+      TransformationInlineFunction(59, {{24, 67}, {25, 68}, {26, 69}});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests called function containing an OpKill instruction.
+  transformation = TransformationInlineFunction(61, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests called function containing an OpUnreachable instruction.
+  transformation = TransformationInlineFunction(63, {});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable transformation.
+  transformation = TransformationInlineFunction(65, {{35, 67},
+                                                     {36, 68},
+                                                     {37, 69},
+                                                     {38, 70},
+                                                     {39, 71},
+                                                     {40, 72},
+                                                     {41, 73},
+                                                     {42, 74},
+                                                     {43, 75},
+                                                     {44, 76},
+                                                     {45, 77},
+                                                     {46, 78},
+                                                     {47, 79},
+                                                     {48, 80},
+                                                     {49, 81},
+                                                     {50, 82},
+                                                     {51, 83}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationInlineFunctionTest, Apply) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %39 "main"
+
+; Types
+          %2 = OpTypeFloat 32
+          %3 = OpTypeVector %2 4
+          %4 = OpTypePointer Function %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFunction %2 %4 %4
+
+; Constant scalars
+          %8 = OpConstant %2 1
+          %9 = OpConstant %2 2
+         %10 = OpConstant %2 3
+         %11 = OpConstant %2 4
+         %12 = OpConstant %2 5
+         %13 = OpConstant %2 6
+         %14 = OpConstant %2 7
+         %15 = OpConstant %2 8
+
+; Constant vectors
+         %16 = OpConstantComposite %3 %8 %9 %10 %11
+         %17 = OpConstantComposite %3 %12 %13 %14 %15
+
+; dot product function
+         %18 = OpFunction %2 None %7
+         %19 = OpFunctionParameter %4
+         %20 = OpFunctionParameter %4
+         %21 = OpLabel
+         %22 = OpLoad %3 %19
+         %23 = OpLoad %3 %20
+         %24 = OpCompositeExtract %2 %22 0
+         %25 = OpCompositeExtract %2 %23 0
+         %26 = OpFMul %2 %24 %25
+         %27 = OpCompositeExtract %2 %22 1
+         %28 = OpCompositeExtract %2 %23 1
+         %29 = OpFMul %2 %27 %28
+         %30 = OpCompositeExtract %2 %22 2
+         %31 = OpCompositeExtract %2 %23 2
+         %32 = OpFMul %2 %30 %31
+         %33 = OpCompositeExtract %2 %22 3
+         %34 = OpCompositeExtract %2 %23 3
+         %35 = OpFMul %2 %33 %34
+         %36 = OpFAdd %2 %26 %29
+         %37 = OpFAdd %2 %32 %36
+         %38 = OpFAdd %2 %35 %37
+               OpReturnValue %38
+               OpFunctionEnd
+
+; main function
+         %39 = OpFunction %5 None %6
+         %40 = OpLabel
+         %41 = OpVariable %4 Function
+         %42 = OpVariable %4 Function
+               OpStore %41 %16
+               OpStore %42 %17
+         %43 = OpFunctionCall %2 %18 %41 %42 ; dot product function call
+               OpBranch %44
+         %44 = OpLabel
+               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);
+  auto transformation = TransformationInlineFunction(43, {{22, 45},
+                                                          {23, 46},
+                                                          {24, 47},
+                                                          {25, 48},
+                                                          {26, 49},
+                                                          {27, 50},
+                                                          {28, 51},
+                                                          {29, 52},
+                                                          {30, 53},
+                                                          {31, 54},
+                                                          {32, 55},
+                                                          {33, 56},
+                                                          {34, 57},
+                                                          {35, 58},
+                                                          {36, 59},
+                                                          {37, 60},
+                                                          {38, 61}});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %39 "main"
+
+; Types
+          %2 = OpTypeFloat 32
+          %3 = OpTypeVector %2 4
+          %4 = OpTypePointer Function %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFunction %2 %4 %4
+
+; Constant scalars
+          %8 = OpConstant %2 1
+          %9 = OpConstant %2 2
+         %10 = OpConstant %2 3
+         %11 = OpConstant %2 4
+         %12 = OpConstant %2 5
+         %13 = OpConstant %2 6
+         %14 = OpConstant %2 7
+         %15 = OpConstant %2 8
+
+; Constant vectors
+         %16 = OpConstantComposite %3 %8 %9 %10 %11
+         %17 = OpConstantComposite %3 %12 %13 %14 %15
+
+; dot product function
+         %18 = OpFunction %2 None %7
+         %19 = OpFunctionParameter %4
+         %20 = OpFunctionParameter %4
+         %21 = OpLabel
+         %22 = OpLoad %3 %19
+         %23 = OpLoad %3 %20
+         %24 = OpCompositeExtract %2 %22 0
+         %25 = OpCompositeExtract %2 %23 0
+         %26 = OpFMul %2 %24 %25
+         %27 = OpCompositeExtract %2 %22 1
+         %28 = OpCompositeExtract %2 %23 1
+         %29 = OpFMul %2 %27 %28
+         %30 = OpCompositeExtract %2 %22 2
+         %31 = OpCompositeExtract %2 %23 2
+         %32 = OpFMul %2 %30 %31
+         %33 = OpCompositeExtract %2 %22 3
+         %34 = OpCompositeExtract %2 %23 3
+         %35 = OpFMul %2 %33 %34
+         %36 = OpFAdd %2 %26 %29
+         %37 = OpFAdd %2 %32 %36
+         %38 = OpFAdd %2 %35 %37
+               OpReturnValue %38
+               OpFunctionEnd
+
+; main function
+         %39 = OpFunction %5 None %6
+         %40 = OpLabel
+         %41 = OpVariable %4 Function
+         %42 = OpVariable %4 Function
+               OpStore %41 %16
+               OpStore %42 %17
+         %45 = OpLoad %3 %41
+         %46 = OpLoad %3 %42
+         %47 = OpCompositeExtract %2 %45 0
+         %48 = OpCompositeExtract %2 %46 0
+         %49 = OpFMul %2 %47 %48
+         %50 = OpCompositeExtract %2 %45 1
+         %51 = OpCompositeExtract %2 %46 1
+         %52 = OpFMul %2 %50 %51
+         %53 = OpCompositeExtract %2 %45 2
+         %54 = OpCompositeExtract %2 %46 2
+         %55 = OpFMul %2 %53 %54
+         %56 = OpCompositeExtract %2 %45 3
+         %57 = OpCompositeExtract %2 %46 3
+         %58 = OpFMul %2 %56 %57
+         %59 = OpFAdd %2 %49 %52
+         %60 = OpFAdd %2 %55 %59
+         %61 = OpFAdd %2 %58 %60
+         %43 = OpCopyObject %2 %61
+               OpBranch %44
+         %44 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+TEST(TransformationInlineFunctionTest, ApplyToMultipleFunctions) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %15 "main"
+
+; Types
+          %2 = OpTypeInt 32 1
+          %3 = OpTypeBool
+          %4 = OpTypePointer Private %2
+          %5 = OpTypePointer Function %2
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFunction %2 %5
+          %9 = OpTypeFunction %2 %2
+
+; Constants
+         %10 = OpConstant %2 0
+         %11 = OpConstant %2 1
+         %12 = OpConstant %2 2
+         %13 = OpConstant %2 3
+
+; Global variable
+         %14 = OpVariable %4 Private
+
+; main function
+         %15 = OpFunction %6 None %7
+         %16 = OpLabel
+         %17 = OpVariable %5 Function
+         %18 = OpVariable %5 Function
+         %19 = OpVariable %5 Function
+               OpStore %17 %13
+         %20 = OpLoad %2 %17
+               OpStore %18 %20
+         %21 = OpFunctionCall %2 %36 %18
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpFunctionCall %2 %36 %18
+               OpStore %17 %21
+         %24 = OpLoad %2 %17
+         %25 = OpFunctionCall %2 %54 %24
+               OpBranch %26
+         %26 = OpLabel
+         %27 = OpFunctionCall %2 %54 %24
+         %28 = OpLoad %2 %17
+         %29 = OpIAdd %2 %28 %25
+               OpStore %17 %29
+         %30 = OpFunctionCall %6 %67
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpFunctionCall %6 %67
+         %33 = OpLoad %2 %14
+         %34 = OpLoad %2 %17
+         %35 = OpIAdd %2 %34 %33
+               OpStore %17 %35
+               OpReturn
+               OpFunctionEnd
+
+; Function %36
+         %36 = OpFunction %2 None %8
+         %37 = OpFunctionParameter %5
+         %38 = OpLabel
+         %39 = OpVariable %5 Function
+         %40 = OpVariable %5 Function
+               OpStore %39 %10
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %52 %49 None
+               OpBranch %42
+         %42 = OpLabel
+         %43 = OpLoad %2 %39
+         %44 = OpLoad %2 %37
+         %45 = OpSLessThan %3 %43 %44
+               OpBranchConditional %45 %46 %52
+         %46 = OpLabel
+         %47 = OpLoad %2 %40
+         %48 = OpIAdd %2 %47 %11
+               OpStore %40 %48
+               OpBranch %49
+         %49 = OpLabel
+         %50 = OpLoad %2 %39
+         %51 = OpIAdd %2 %50 %12
+               OpStore %39 %51
+               OpBranch %41
+         %52 = OpLabel
+         %53 = OpLoad %2 %40
+               OpReturnValue %53
+               OpFunctionEnd
+
+; Function %54
+         %54 = OpFunction %2 None %9
+         %55 = OpFunctionParameter %2
+         %56 = OpLabel
+         %57 = OpVariable %5 Function
+               OpStore %57 %10
+         %58 = OpSGreaterThan %3 %55 %10
+               OpSelectionMerge %62 None
+               OpBranchConditional %58 %64 %59
+         %59 = OpLabel
+         %60 = OpLoad %2 %57
+         %61 = OpISub %2 %60 %12
+               OpStore %57 %61
+               OpBranch %62
+         %62 = OpLabel
+         %63 = OpLoad %2 %57
+               OpReturnValue %63
+         %64 = OpLabel
+         %65 = OpLoad %2 %57
+         %66 = OpIAdd %2 %65 %11
+               OpStore %57 %66
+               OpBranch %62
+               OpFunctionEnd
+
+; Function %67
+         %67 = OpFunction %6 None %7
+         %68 = OpLabel
+               OpStore %14 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  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 = TransformationInlineFunction(30, {});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  // Tests a parameter included in the id map.
+  transformation = TransformationInlineFunction(25, {{55, 69},
+                                                     {56, 70},
+                                                     {57, 71},
+                                                     {58, 72},
+                                                     {59, 73},
+                                                     {60, 74},
+                                                     {61, 75},
+                                                     {62, 76},
+                                                     {63, 77},
+                                                     {64, 78},
+                                                     {65, 79},
+                                                     {66, 80}});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  // Tests the id of the returned value not included in the id map.
+  transformation = TransformationInlineFunction(25, {{56, 69},
+                                                     {57, 70},
+                                                     {58, 71},
+                                                     {59, 72},
+                                                     {60, 73},
+                                                     {61, 74},
+                                                     {62, 75},
+                                                     {64, 76},
+                                                     {65, 77},
+                                                     {66, 78}});
+  ASSERT_DEATH(
+      transformation.IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  transformation = TransformationInlineFunction(25, {{57, 69},
+                                                     {58, 70},
+                                                     {59, 71},
+                                                     {60, 72},
+                                                     {61, 73},
+                                                     {62, 74},
+                                                     {63, 75},
+                                                     {64, 76},
+                                                     {65, 77},
+                                                     {66, 78}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationInlineFunction(21, {{39, 79},
+                                                     {40, 80},
+                                                     {41, 81},
+                                                     {42, 82},
+                                                     {43, 83},
+                                                     {44, 84},
+                                                     {45, 85},
+                                                     {46, 86},
+                                                     {47, 87},
+                                                     {48, 88},
+                                                     {49, 89},
+                                                     {50, 90},
+                                                     {51, 91},
+                                                     {52, 92},
+                                                     {53, 93}});
+  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 Vertex %15 "main"
+
+; Types
+          %2 = OpTypeInt 32 1
+          %3 = OpTypeBool
+          %4 = OpTypePointer Private %2
+          %5 = OpTypePointer Function %2
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+          %8 = OpTypeFunction %2 %5
+          %9 = OpTypeFunction %2 %2
+
+; Constants
+         %10 = OpConstant %2 0
+         %11 = OpConstant %2 1
+         %12 = OpConstant %2 2
+         %13 = OpConstant %2 3
+
+; Global variable
+         %14 = OpVariable %4 Private
+
+; main function
+         %15 = OpFunction %6 None %7
+         %16 = OpLabel
+         %80 = OpVariable %5 Function
+         %79 = OpVariable %5 Function
+         %69 = OpVariable %5 Function
+         %17 = OpVariable %5 Function
+         %18 = OpVariable %5 Function
+         %19 = OpVariable %5 Function
+               OpStore %17 %13
+         %20 = OpLoad %2 %17
+               OpStore %18 %20
+               OpStore %79 %10
+               OpBranch %81
+         %81 = OpLabel
+               OpLoopMerge %92 %89 None
+               OpBranch %82
+         %82 = OpLabel
+         %83 = OpLoad %2 %79
+         %84 = OpLoad %2 %18
+         %85 = OpSLessThan %3 %83 %84
+               OpBranchConditional %85 %86 %92
+         %86 = OpLabel
+         %87 = OpLoad %2 %80
+         %88 = OpIAdd %2 %87 %11
+               OpStore %80 %88
+               OpBranch %89
+         %89 = OpLabel
+         %90 = OpLoad %2 %79
+         %91 = OpIAdd %2 %90 %12
+               OpStore %79 %91
+               OpBranch %81
+         %92 = OpLabel
+         %93 = OpLoad %2 %80
+         %21 = OpCopyObject %2 %93
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpFunctionCall %2 %36 %18
+               OpStore %17 %21
+         %24 = OpLoad %2 %17
+               OpStore %69 %10
+         %70 = OpSGreaterThan %3 %24 %10
+               OpSelectionMerge %74 None
+               OpBranchConditional %70 %76 %71
+         %71 = OpLabel
+         %72 = OpLoad %2 %69
+         %73 = OpISub %2 %72 %12
+               OpStore %69 %73
+               OpBranch %74
+         %74 = OpLabel
+         %75 = OpLoad %2 %69
+         %25 = OpCopyObject %2 %75
+               OpBranch %26
+         %76 = OpLabel
+         %77 = OpLoad %2 %69
+         %78 = OpIAdd %2 %77 %11
+               OpStore %69 %78
+               OpBranch %74
+         %26 = OpLabel
+         %27 = OpFunctionCall %2 %54 %24
+         %28 = OpLoad %2 %17
+         %29 = OpIAdd %2 %28 %25
+               OpStore %17 %29
+               OpStore %14 %12
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpFunctionCall %6 %67
+         %33 = OpLoad %2 %14
+         %34 = OpLoad %2 %17
+         %35 = OpIAdd %2 %34 %33
+               OpStore %17 %35
+               OpReturn
+               OpFunctionEnd
+
+; Function %36
+         %36 = OpFunction %2 None %8
+         %37 = OpFunctionParameter %5
+         %38 = OpLabel
+         %39 = OpVariable %5 Function
+         %40 = OpVariable %5 Function
+               OpStore %39 %10
+               OpBranch %41
+         %41 = OpLabel
+               OpLoopMerge %52 %49 None
+               OpBranch %42
+         %42 = OpLabel
+         %43 = OpLoad %2 %39
+         %44 = OpLoad %2 %37
+         %45 = OpSLessThan %3 %43 %44
+               OpBranchConditional %45 %46 %52
+         %46 = OpLabel
+         %47 = OpLoad %2 %40
+         %48 = OpIAdd %2 %47 %11
+               OpStore %40 %48
+               OpBranch %49
+         %49 = OpLabel
+         %50 = OpLoad %2 %39
+         %51 = OpIAdd %2 %50 %12
+               OpStore %39 %51
+               OpBranch %41
+         %52 = OpLabel
+         %53 = OpLoad %2 %40
+               OpReturnValue %53
+               OpFunctionEnd
+
+; Function %54
+         %54 = OpFunction %2 None %9
+         %55 = OpFunctionParameter %2
+         %56 = OpLabel
+         %57 = OpVariable %5 Function
+               OpStore %57 %10
+         %58 = OpSGreaterThan %3 %55 %10
+               OpSelectionMerge %62 None
+               OpBranchConditional %58 %64 %59
+         %59 = OpLabel
+         %60 = OpLoad %2 %57
+         %61 = OpISub %2 %60 %12
+               OpStore %57 %61
+               OpBranch %62
+         %62 = OpLabel
+         %63 = OpLoad %2 %57
+               OpReturnValue %63
+         %64 = OpLabel
+         %65 = OpLoad %2 %57
+         %66 = OpIAdd %2 %65 %11
+               OpStore %57 %66
+               OpBranch %62
+               OpFunctionEnd
+
+; Function %67
+         %67 = OpFunction %6 None %7
+         %68 = OpLabel
+               OpStore %14 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+TEST(TransformationInlineFunctionTest, HandlesOpPhisInTheSecondBlock) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpUndef %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %6 = OpFunctionCall %2 %7
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %7 = OpFunction %2 None %3
+          %8 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %12 = OpPhi %10 %11 %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));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  TransformationInlineFunction transformation(6,
+                                              {{{8, 20}, {13, 21}, {12, 22}}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %10 = OpTypeInt 32 0
+         %11 = OpUndef %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+         %22 = OpPhi %10 %11 %5
+               OpBranch %14
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+          %7 = OpFunction %2 None %3
+          %8 = OpLabel
+               OpBranch %13
+         %13 = OpLabel
+         %12 = OpPhi %10 %11 %8
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationInlineFunctionTest, OverflowIds) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %39 "main"
+
+; Types
+          %2 = OpTypeFloat 32
+          %3 = OpTypeVector %2 4
+          %4 = OpTypePointer Function %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFunction %2 %4 %4
+
+; Constant scalars
+          %8 = OpConstant %2 1
+          %9 = OpConstant %2 2
+         %10 = OpConstant %2 3
+         %11 = OpConstant %2 4
+         %12 = OpConstant %2 5
+         %13 = OpConstant %2 6
+         %14 = OpConstant %2 7
+         %15 = OpConstant %2 8
+
+; Constant vectors
+         %16 = OpConstantComposite %3 %8 %9 %10 %11
+         %17 = OpConstantComposite %3 %12 %13 %14 %15
+
+; dot product function
+         %18 = OpFunction %2 None %7
+         %19 = OpFunctionParameter %4
+         %20 = OpFunctionParameter %4
+         %21 = OpLabel
+         %22 = OpLoad %3 %19
+         %23 = OpLoad %3 %20
+         %24 = OpCompositeExtract %2 %22 0
+         %25 = OpCompositeExtract %2 %23 0
+         %26 = OpFMul %2 %24 %25
+         %27 = OpCompositeExtract %2 %22 1
+         %28 = OpCompositeExtract %2 %23 1
+         %29 = OpFMul %2 %27 %28
+               OpBranch %100
+        %100 = OpLabel
+         %30 = OpCompositeExtract %2 %22 2
+         %31 = OpCompositeExtract %2 %23 2
+         %32 = OpFMul %2 %30 %31
+         %33 = OpCompositeExtract %2 %22 3
+         %34 = OpCompositeExtract %2 %23 3
+         %35 = OpFMul %2 %33 %34
+         %36 = OpFAdd %2 %26 %29
+         %37 = OpFAdd %2 %32 %36
+         %38 = OpFAdd %2 %35 %37
+               OpReturnValue %38
+               OpFunctionEnd
+
+; main function
+         %39 = OpFunction %5 None %6
+         %40 = OpLabel
+         %41 = OpVariable %4 Function
+         %42 = OpVariable %4 Function
+               OpStore %41 %16
+               OpStore %42 %17
+         %43 = OpFunctionCall %2 %18 %41 %42 ; dot product function call
+               OpBranch %44
+         %44 = OpLabel
+               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));
+  auto overflow_ids_unique_ptr = MakeUnique<CounterOverflowIdSource>(1000);
+  auto overflow_ids_ptr = overflow_ids_unique_ptr.get();
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options,
+      std::move(overflow_ids_unique_ptr));
+  auto transformation = TransformationInlineFunction(43, {{22, 45},
+                                                          {23, 46},
+                                                          {24, 47},
+                                                          {25, 48},
+                                                          {26, 49},
+                                                          {27, 50},
+                                                          {28, 51},
+                                                          {29, 52}});
+
+  // The following ids are left un-mapped; overflow ids will be required for
+  // them: 30, 31, 32, 33, 34, 35, 36, 37, 38, 100
+
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context,
+                        overflow_ids_ptr->GetIssuedOverflowIds());
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %39 "main"
+
+; Types
+          %2 = OpTypeFloat 32
+          %3 = OpTypeVector %2 4
+          %4 = OpTypePointer Function %3
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFunction %2 %4 %4
+
+; Constant scalars
+          %8 = OpConstant %2 1
+          %9 = OpConstant %2 2
+         %10 = OpConstant %2 3
+         %11 = OpConstant %2 4
+         %12 = OpConstant %2 5
+         %13 = OpConstant %2 6
+         %14 = OpConstant %2 7
+         %15 = OpConstant %2 8
+
+; Constant vectors
+         %16 = OpConstantComposite %3 %8 %9 %10 %11
+         %17 = OpConstantComposite %3 %12 %13 %14 %15
+
+; dot product function
+         %18 = OpFunction %2 None %7
+         %19 = OpFunctionParameter %4
+         %20 = OpFunctionParameter %4
+         %21 = OpLabel
+         %22 = OpLoad %3 %19
+         %23 = OpLoad %3 %20
+         %24 = OpCompositeExtract %2 %22 0
+         %25 = OpCompositeExtract %2 %23 0
+         %26 = OpFMul %2 %24 %25
+         %27 = OpCompositeExtract %2 %22 1
+         %28 = OpCompositeExtract %2 %23 1
+         %29 = OpFMul %2 %27 %28
+               OpBranch %100
+        %100 = OpLabel
+         %30 = OpCompositeExtract %2 %22 2
+         %31 = OpCompositeExtract %2 %23 2
+         %32 = OpFMul %2 %30 %31
+         %33 = OpCompositeExtract %2 %22 3
+         %34 = OpCompositeExtract %2 %23 3
+         %35 = OpFMul %2 %33 %34
+         %36 = OpFAdd %2 %26 %29
+         %37 = OpFAdd %2 %32 %36
+         %38 = OpFAdd %2 %35 %37
+               OpReturnValue %38
+               OpFunctionEnd
+
+; main function
+         %39 = OpFunction %5 None %6
+         %40 = OpLabel
+         %41 = OpVariable %4 Function
+         %42 = OpVariable %4 Function
+               OpStore %41 %16
+               OpStore %42 %17
+         %45 = OpLoad %3 %41
+         %46 = OpLoad %3 %42
+         %47 = OpCompositeExtract %2 %45 0
+         %48 = OpCompositeExtract %2 %46 0
+         %49 = OpFMul %2 %47 %48
+         %50 = OpCompositeExtract %2 %45 1
+         %51 = OpCompositeExtract %2 %46 1
+         %52 = OpFMul %2 %50 %51
+               OpBranch %1000
+       %1000 = OpLabel
+       %1001 = OpCompositeExtract %2 %45 2
+       %1002 = OpCompositeExtract %2 %46 2
+       %1003 = OpFMul %2 %1001 %1002
+       %1004 = OpCompositeExtract %2 %45 3
+       %1005 = OpCompositeExtract %2 %46 3
+       %1006 = OpFMul %2 %1004 %1005
+       %1007 = OpFAdd %2 %49 %52
+       %1008 = OpFAdd %2 %1003 %1007
+       %1009 = OpFAdd %2 %1006 %1008
+         %43 = OpCopyObject %2 %1009
+               OpBranch %44
+         %44 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+TEST(TransformationInlineFunctionTest, OpPhiInBlockFollowingCall) {
+  // This test checks that if the block after the inlined function call has an
+  // OpPhi instruction and the called function contains multiple blocks then the
+  // OpPhi instruction gets updated to refer to the return block of the inlined
+  // function, since the block containing the call will no longer be a
+  // predecessor.
+
+  std::string reference_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
+         %13 = OpTypeBool
+         %14 = OpConstantTrue %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+          %8 = OpFunctionCall %2 %6
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpPhi %13 %14 %10
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               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);
+  auto transformation = TransformationInlineFunction(8, {{7, 100}, {20, 101}});
+
+  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 variant_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
+         %13 = OpTypeBool
+         %14 = OpConstantTrue %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+         %12 = OpPhi %13 %14 %101
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_invert_comparison_operator_test.cpp b/test/fuzz/transformation_invert_comparison_operator_test.cpp
index 0468469..3970f83 100644
--- a/test/fuzz/transformation_invert_comparison_operator_test.cpp
+++ b/test/fuzz/transformation_invert_comparison_operator_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_invert_comparison_operator.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -57,13 +59,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Operator id is not valid.
   ASSERT_FALSE(TransformationInvertComparisonOperator(23, 23).IsApplicable(
       context.get(), transformation_context));
@@ -82,7 +82,8 @@
                                                           fresh_id);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   std::string expected = R"(
diff --git a/test/fuzz/transformation_load_test.cpp b/test/fuzz/transformation_load_test.cpp
index 18ca195..a03ffdd 100644
--- a/test/fuzz/transformation_load_test.cpp
+++ b/test/fuzz/transformation_load_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_load.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -82,13 +85,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
       27);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
@@ -189,8 +190,10 @@
         100, 33, MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -198,8 +201,10 @@
         101, 46, MakeInstructionDescriptor(16, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -207,8 +212,10 @@
         102, 16, MakeInstructionDescriptor(16, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -216,8 +223,10 @@
         103, 40, MakeInstructionDescriptor(43, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_make_vector_operation_dynamic_test.cpp b/test/fuzz/transformation_make_vector_operation_dynamic_test.cpp
new file mode 100644
index 0000000..064759a
--- /dev/null
+++ b/test/fuzz/transformation_make_vector_operation_dynamic_test.cpp
@@ -0,0 +1,364 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_make_vector_operation_dynamic.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(TransformationMakeVectorOperationDynamicTest, IsApplicable) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %22 "main"
+
+; Types
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeFloat 32
+          %6 = OpTypeVector %5 2
+          %7 = OpTypeVector %5 3
+          %8 = OpTypeVector %5 4
+          %9 = OpTypeMatrix %6 2
+
+; Constant scalars
+         %10 = OpConstant %4 0
+         %11 = OpConstant %4 1
+         %12 = OpConstant %4 2
+         %13 = OpConstant %5 0
+         %14 = OpConstant %5 1
+         %15 = OpConstant %5 2
+         %16 = OpConstant %5 3
+
+; Constant composites
+         %17 = OpConstantComposite %6 %13 %14
+         %18 = OpConstantComposite %6 %15 %16
+         %19 = OpConstantComposite %7 %13 %14 %15
+         %20 = OpConstantComposite %8 %13 %14 %15 %16
+         %21 = OpConstantComposite %9 %17 %18
+
+; main function
+         %22 = OpFunction %2 None %3
+         %23 = OpLabel
+         %24 = OpCompositeExtract %5 %17 0
+         %25 = OpCompositeExtract %5 %17 1
+         %26 = OpCompositeExtract %5 %18 0
+         %27 = OpCompositeExtract %5 %18 1
+         %28 = OpCompositeExtract %5 %19 0
+         %29 = OpCompositeExtract %5 %19 1
+         %30 = OpCompositeExtract %5 %19 2
+         %31 = OpCompositeExtract %5 %20 0
+         %32 = OpCompositeExtract %5 %20 1
+         %33 = OpCompositeExtract %5 %20 2
+         %34 = OpCompositeExtract %5 %20 3
+         %35 = OpCompositeExtract %6 %21 0
+         %36 = OpCompositeExtract %6 %21 1
+         %37 = OpCompositeInsert %6 %15 %17 0
+         %38 = OpCompositeInsert %6 %16 %17 1
+         %39 = OpCompositeInsert %6 %13 %18 0
+         %40 = OpCompositeInsert %6 %14 %18 1
+         %41 = OpCompositeInsert %7 %13 %19 0
+         %42 = OpCompositeInsert %7 %14 %19 1
+         %43 = OpCompositeInsert %7 %15 %19 2
+         %44 = OpCompositeInsert %8 %13 %20 0
+         %45 = OpCompositeInsert %8 %14 %20 1
+         %46 = OpCompositeInsert %8 %15 %20 2
+         %47 = OpCompositeInsert %8 %16 %20 3
+         %48 = OpCompositeInsert %9 %17 %21 0
+         %49 = OpCompositeInsert %9 %18 %21 1
+               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);
+  // Tests undefined instruction.
+  auto transformation = TransformationMakeVectorOperationDynamic(50, 10);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests non-composite instruction.
+  transformation = TransformationMakeVectorOperationDynamic(23, 11);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests composite being a matrix.
+  transformation = TransformationMakeVectorOperationDynamic(48, 12);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests literal not defined as constant.
+  transformation = TransformationMakeVectorOperationDynamic(34, 51);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable instructions.
+  transformation = TransformationMakeVectorOperationDynamic(24, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationMakeVectorOperationDynamic(25, 11);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationMakeVectorOperationDynamic(26, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationMakeVectorOperationDynamic(37, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationMakeVectorOperationDynamic(38, 11);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationMakeVectorOperationDynamic(39, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationMakeVectorOperationDynamicTest, Apply) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %20 "main"
+
+; Types
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeFloat 32
+          %6 = OpTypeVector %5 2
+          %7 = OpTypeVector %5 3
+          %8 = OpTypeVector %5 4
+
+; Constant scalars
+          %9 = OpConstant %4 0
+         %10 = OpConstant %4 1
+         %11 = OpConstant %4 2
+         %12 = OpConstant %4 3
+         %13 = OpConstant %5 0
+         %14 = OpConstant %5 1
+         %15 = OpConstant %5 2
+         %16 = OpConstant %5 3
+
+; Constant vectors
+         %17 = OpConstantComposite %6 %13 %14
+         %18 = OpConstantComposite %7 %13 %14 %15
+         %19 = OpConstantComposite %8 %13 %14 %15 %16
+
+; main function
+         %20 = OpFunction %2 None %3
+         %21 = OpLabel
+         %22 = OpCompositeExtract %5 %17 0
+         %23 = OpCompositeExtract %5 %17 1
+         %24 = OpCompositeExtract %5 %18 0
+         %25 = OpCompositeExtract %5 %18 1
+         %26 = OpCompositeExtract %5 %18 2
+         %27 = OpCompositeExtract %5 %19 0
+         %28 = OpCompositeExtract %5 %19 1
+         %29 = OpCompositeExtract %5 %19 2
+         %30 = OpCompositeExtract %5 %19 3
+         %31 = OpCompositeInsert %6 %13 %17 0
+         %32 = OpCompositeInsert %6 %14 %17 1
+         %33 = OpCompositeInsert %7 %13 %18 0
+         %34 = OpCompositeInsert %7 %14 %18 1
+         %35 = OpCompositeInsert %7 %15 %18 2
+         %36 = OpCompositeInsert %8 %13 %19 0
+         %37 = OpCompositeInsert %8 %14 %19 1
+         %38 = OpCompositeInsert %8 %15 %19 2
+         %39 = OpCompositeInsert %8 %16 %19 3
+               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);
+  auto transformation = TransformationMakeVectorOperationDynamic(22, 9);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(23, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(24, 9);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(25, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(26, 11);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(27, 9);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(28, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(29, 11);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(30, 12);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(31, 9);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(32, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(33, 9);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(34, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(35, 11);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(36, 9);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(37, 10);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(38, 11);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  transformation = TransformationMakeVectorOperationDynamic(39, 12);
+  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 Vertex %20 "main"
+
+; Types
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeInt 32 0
+          %5 = OpTypeFloat 32
+          %6 = OpTypeVector %5 2
+          %7 = OpTypeVector %5 3
+          %8 = OpTypeVector %5 4
+
+; Constant scalars
+          %9 = OpConstant %4 0
+         %10 = OpConstant %4 1
+         %11 = OpConstant %4 2
+         %12 = OpConstant %4 3
+         %13 = OpConstant %5 0
+         %14 = OpConstant %5 1
+         %15 = OpConstant %5 2
+         %16 = OpConstant %5 3
+
+; Constant vectors
+         %17 = OpConstantComposite %6 %13 %14
+         %18 = OpConstantComposite %7 %13 %14 %15
+         %19 = OpConstantComposite %8 %13 %14 %15 %16
+
+; main function
+         %20 = OpFunction %2 None %3
+         %21 = OpLabel
+         %22 = OpVectorExtractDynamic %5 %17 %9
+         %23 = OpVectorExtractDynamic %5 %17 %10
+         %24 = OpVectorExtractDynamic %5 %18 %9
+         %25 = OpVectorExtractDynamic %5 %18 %10
+         %26 = OpVectorExtractDynamic %5 %18 %11
+         %27 = OpVectorExtractDynamic %5 %19 %9
+         %28 = OpVectorExtractDynamic %5 %19 %10
+         %29 = OpVectorExtractDynamic %5 %19 %11
+         %30 = OpVectorExtractDynamic %5 %19 %12
+         %31 = OpVectorInsertDynamic %6 %17 %13 %9
+         %32 = OpVectorInsertDynamic %6 %17 %14 %10
+         %33 = OpVectorInsertDynamic %7 %18 %13 %9
+         %34 = OpVectorInsertDynamic %7 %18 %14 %10
+         %35 = OpVectorInsertDynamic %7 %18 %15 %11
+         %36 = OpVectorInsertDynamic %8 %19 %13 %9
+         %37 = OpVectorInsertDynamic %8 %19 %14 %10
+         %38 = OpVectorInsertDynamic %8 %19 %15 %11
+         %39 = OpVectorInsertDynamic %8 %19 %16 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_merge_blocks_test.cpp b/test/fuzz/transformation_merge_blocks_test.cpp
index 4500445..d5289d3 100644
--- a/test/fuzz/transformation_merge_blocks_test.cpp
+++ b/test/fuzz/transformation_merge_blocks_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_merge_blocks.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -42,13 +44,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_FALSE(TransformationMergeBlocks(3).IsApplicable(
       context.get(), transformation_context));
   ASSERT_FALSE(TransformationMergeBlocks(7).IsApplicable(
@@ -84,13 +84,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_FALSE(TransformationMergeBlocks(6).IsApplicable(
       context.get(), transformation_context));
 }
@@ -125,13 +123,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_FALSE(TransformationMergeBlocks(10).IsApplicable(
       context.get(), transformation_context));
 }
@@ -167,18 +163,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationMergeBlocks transformation(10);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -241,18 +236,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationMergeBlocks transformation(10);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -320,18 +314,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationMergeBlocks transformation(11);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -395,18 +388,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationMergeBlocks transformation(6);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -476,20 +468,20 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   for (auto& transformation :
        {TransformationMergeBlocks(100), TransformationMergeBlocks(101),
         TransformationMergeBlocks(102), TransformationMergeBlocks(103)}) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
@@ -568,19 +560,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   for (auto& transformation :
        {TransformationMergeBlocks(101), TransformationMergeBlocks(100)}) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
@@ -659,18 +651,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationMergeBlocks transformation(101);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_merge_function_returns_test.cpp b/test/fuzz/transformation_merge_function_returns_test.cpp
new file mode 100644
index 0000000..e60d345
--- /dev/null
+++ b/test/fuzz/transformation_merge_function_returns_test.cpp
@@ -0,0 +1,1886 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_merge_function_returns.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/counter_overflow_id_source.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+protobufs::ReturnMergingInfo MakeReturnMergingInfo(
+    uint32_t merge_block_id, uint32_t is_returning_id,
+    uint32_t maybe_return_val_id,
+    const std::map<uint32_t, uint32_t>& opphi_to_suitable_id) {
+  protobufs::ReturnMergingInfo result;
+  result.set_merge_block_id(merge_block_id);
+  result.set_is_returning_id(is_returning_id);
+  result.set_maybe_return_val_id(maybe_return_val_id);
+  *result.mutable_opphi_to_suitable_id() =
+      fuzzerutil::MapToRepeatedUInt32Pair(opphi_to_suitable_id);
+  return result;
+}
+
+TEST(TransformationMergeFunctionReturnsTest, SimpleInapplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypeFunction %7
+          %9 = OpTypeBool
+         %10 = OpConstantTrue %9
+         %11 = OpConstantFalse %9
+         %12 = OpConstant %5 0
+         %13 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+         %15 = OpFunctionCall %3 %16
+         %17 = OpFunctionCall %3 %18
+         %19 = OpFunctionCall %3 %20
+         %21 = OpFunctionCall %7 %22
+               OpReturn
+               OpFunctionEnd
+         %16 = OpFunction %3 None %4
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %10 %25 %26
+         %25 = OpLabel
+               OpReturn
+         %26 = OpLabel
+               OpReturn
+         %24 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %27 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpLoopMerge %29 %30 None
+               OpBranch %31
+         %31 = OpLabel
+               OpBranchConditional %10 %32 %29
+         %32 = OpLabel
+               OpReturn
+         %30 = OpLabel
+               OpBranch %28
+         %29 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %3 None %4
+         %33 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %35 %36 None
+               OpBranch %37
+         %37 = OpLabel
+               OpBranchConditional %10 %38 %35
+         %38 = OpLabel
+               OpReturn
+         %36 = OpLabel
+               OpBranch %34
+         %35 = OpLabel
+         %39 = OpFunctionCall %3 %18
+               OpReturn
+               OpFunctionEnd
+         %22 = OpFunction %7 None %8
+         %40 = OpLabel
+               OpBranch %51
+         %51 = OpLabel
+               OpLoopMerge %41 %53 None
+               OpBranchConditional %10 %42 %41
+         %42 = OpLabel
+         %43 = OpConvertSToF %7 %12
+               OpReturnValue %43
+         %41 = OpLabel
+         %44 = OpConvertSToF %7 %13
+               OpReturnValue %44
+         %53 = OpLabel
+               OpBranch %51
+               OpFunctionEnd
+         %45 = OpFunction %5 None %6
+         %46 = OpLabel
+               OpBranch %52
+         %52 = OpLabel
+         %47 = OpConvertSToF %7 %13
+               OpLoopMerge %48 %54 None
+               OpBranchConditional %10 %49 %48
+         %49 = OpLabel
+               OpReturnValue %12
+         %48 = OpLabel
+         %50 = OpCopyObject %5 %12
+               OpReturnValue %13
+         %54 = OpLabel
+               OpBranch %52
+               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);
+
+  // Function %1 does not exist.
+  ASSERT_FALSE(TransformationMergeFunctionReturns(1, 100, 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, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Block %28 is the merge block of a loop containing a return instruction, but
+  // it contains an OpReturn instruction (so, it contains instructions that are
+  // not OpLabel, OpPhi or OpBranch).
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          18, 100, 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
+  // it contains an OpFunctionCall instruction (so, it contains instructions
+  // that are not OpLabel, OpPhi or OpBranch).
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          20, 100, 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));
+
+  // 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));
+
+  // 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));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, MissingBooleans) {
+  {
+    // OpConstantTrue is missing.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "A("
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeFunction %7
+          %9 = OpTypeBool
+         %10 = OpConstantFalse %9
+         %11 = OpConstant %7 1
+         %12 = OpConstant %7 2
+          %2 = OpFunction %5 None %6
+         %13 = OpLabel
+          %4 = OpFunctionCall %7 %3
+               OpReturn
+               OpFunctionEnd
+          %3 = OpFunction %7 None %8
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %10 %17 %16
+         %17 = OpLabel
+               OpReturnValue %11
+         %16 = OpLabel
+               OpReturnValue %12
+               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);
+
+    ASSERT_FALSE(TransformationMergeFunctionReturns(3, 100, 101, 0, 0, {{}})
+                     .IsApplicable(context.get(), transformation_context));
+  }
+  {
+    // OpConstantFalse is missing.
+    std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "A("
+               OpDecorate %3 RelaxedPrecision
+               OpDecorate %4 RelaxedPrecision
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeFunction %7
+          %9 = OpTypeBool
+         %10 = OpConstantTrue %9
+         %11 = OpConstant %7 1
+         %12 = OpConstant %7 2
+          %2 = OpFunction %5 None %6
+         %13 = OpLabel
+          %4 = OpFunctionCall %7 %3
+               OpReturn
+               OpFunctionEnd
+          %3 = OpFunction %7 None %8
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %10 %17 %16
+         %17 = OpLabel
+               OpReturnValue %11
+         %16 = OpLabel
+               OpReturnValue %12
+               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);
+
+    ASSERT_FALSE(TransformationMergeFunctionReturns(3, 100, 101, 0, 0, {{}})
+                     .IsApplicable(context.get(), transformation_context));
+  }
+}
+
+TEST(TransformationMergeFunctionReturnsTest, InvalidIds) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+         %42 = OpTypeFloat 32
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 0
+         %11 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %12 = OpLabel
+         %13 = OpFunctionCall %5 %14
+         %15 = OpFunctionCall %3 %16
+               OpReturn
+               OpFunctionEnd
+         %17 = OpFunction %3 None %4
+         %18 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+               OpLoopMerge %20 %21 None
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %8 %23 %20
+         %23 = OpLabel
+               OpReturn
+         %21 = OpLabel
+               OpBranch %19
+         %20 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %5 None %6
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpLoopMerge %27 %28 None
+               OpBranch %29
+         %29 = OpLabel
+               OpBranchConditional %8 %30 %27
+         %30 = OpLabel
+               OpReturnValue %10
+         %28 = OpLabel
+               OpBranch %26
+         %27 = OpLabel
+               OpBranch %33
+         %33 = OpLabel
+               OpReturnValue %11
+               OpFunctionEnd
+         %16 = OpFunction %3 None %4
+         %34 = OpLabel
+               OpBranch %35
+         %35 = OpLabel
+               OpLoopMerge %36 %37 None
+               OpBranch %38
+         %38 = OpLabel
+         %43 = OpConvertSToF %42 %10
+               OpBranchConditional %8 %39 %36
+         %39 = OpLabel
+               OpReturn
+         %37 = OpLabel
+         %44 = OpConvertSToF %42 %10
+               OpBranch %35
+         %36 = OpLabel
+         %31 = OpPhi %42 %43 %38
+         %32 = OpPhi %5 %11 %38
+               OpBranch %40
+         %40 = OpLabel
+         %41 = OpFunctionCall %3 %17
+               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);
+
+  // Fresh id %100 is used twice.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          17, 100, 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, {{}})}})
+          .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, {{}})}})
+          .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, {{}})}})
+          .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, {{}})}})
+          .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));
+
+  // %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));
+
+  // Fresh id %102 is repeated.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          14, 100, 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,
+          {{MakeReturnMergingInfo(36, 103, 104, {{{31, 11}, {32, 11}}})}})
+          .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,
+          {{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,
+          {{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,
+                   {{MakeReturnMergingInfo(36, 102, 0, {{{32, 11}}})}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Id %1000 cannot be found in the module and no suitable id for OpPhi %31 is
+  // available at the end of the entry block.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          16, 100, 101, 0, 0,
+          {{MakeReturnMergingInfo(36, 102, 0, {{{31, 1000}, {32, 11}}})}})
+          .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, Simple) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypeFunction %7 %7
+          %9 = OpTypeFunction %7
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %40 = OpConstantFalse %10
+         %12 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %3 None %4
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %11 %18 %17
+         %18 = OpLabel
+               OpReturn
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %19 = OpFunction %5 None %6
+         %20 = OpLabel
+               OpBranch %21
+         %21 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %11 %23 %24
+         %23 = OpLabel
+               OpReturnValue %12
+         %24 = OpLabel
+         %25 = OpIAdd %5 %12 %12
+               OpReturnValue %25
+         %22 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+         %26 = OpFunction %7 None %8
+         %27 = OpFunctionParameter %7
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpSelectionMerge %30 None
+               OpBranchConditional %11 %31 %30
+         %31 = OpLabel
+         %32 = OpFAdd %7 %27 %27
+               OpReturnValue %32
+         %30 = OpLabel
+               OpReturnValue %27
+               OpFunctionEnd
+         %33 = OpFunction %7 None %9
+         %34 = OpLabel
+         %35 = OpConvertSToF %7 %12
+               OpBranch %36
+         %36 = OpLabel
+               OpSelectionMerge %37 None
+               OpBranchConditional %11 %38 %37
+         %38 = OpLabel
+         %39 = OpFAdd %7 %35 %35
+               OpReturnValue %39
+         %37 = OpLabel
+               OpReturnValue %35
+               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);
+
+  // The 0s are allowed because the function's return type is void.
+  auto transformation1 =
+      TransformationMergeFunctionReturns(14, 100, 101, 0, 0, {{}});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %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));
+
+  // %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, {{}});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %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));
+
+  // %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, {{}});
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // %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));
+
+  // %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, {{}});
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeFloat 32
+          %8 = OpTypeFunction %7 %7
+          %9 = OpTypeFunction %7
+         %10 = OpTypeBool
+         %11 = OpConstantTrue %10
+         %40 = OpConstantFalse %10
+         %12 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %3 None %4
+         %15 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %11 %16 %100
+         %16 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %11 %18 %17
+         %18 = OpLabel
+               OpBranch %101
+         %17 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %19 = OpFunction %5 None %6
+         %20 = OpLabel
+               OpBranch %110
+        %110 = OpLabel
+               OpLoopMerge %111 %110 None
+               OpBranchConditional %11 %21 %110
+         %21 = OpLabel
+               OpSelectionMerge %22 None
+               OpBranchConditional %11 %23 %24
+         %23 = OpLabel
+               OpBranch %111
+         %24 = OpLabel
+         %25 = OpIAdd %5 %12 %12
+               OpBranch %111
+         %22 = OpLabel
+               OpUnreachable
+        %111 = OpLabel
+        %112 = OpPhi %5 %12 %23 %25 %24
+               OpReturnValue %112
+               OpFunctionEnd
+         %26 = OpFunction %7 None %8
+         %27 = OpFunctionParameter %7
+         %28 = OpLabel
+               OpBranch %120
+        %120 = OpLabel
+               OpLoopMerge %121 %120 None
+               OpBranchConditional %11 %29 %120
+         %29 = OpLabel
+               OpSelectionMerge %30 None
+               OpBranchConditional %11 %31 %30
+         %31 = OpLabel
+         %32 = OpFAdd %7 %27 %27
+               OpBranch %121
+         %30 = OpLabel
+               OpBranch %121
+        %121 = OpLabel
+        %122 = OpPhi %7 %27 %30 %32 %31
+               OpReturnValue %122
+               OpFunctionEnd
+         %33 = OpFunction %7 None %9
+         %34 = OpLabel
+         %35 = OpConvertSToF %7 %12
+               OpBranch %130
+        %130 = OpLabel
+               OpLoopMerge %131 %130 None
+               OpBranchConditional %11 %36 %130
+         %36 = OpLabel
+               OpSelectionMerge %37 None
+               OpBranchConditional %11 %38 %37
+         %38 = OpLabel
+         %39 = OpFAdd %7 %35 %35
+               OpBranch %131
+         %37 = OpLabel
+               OpBranch %131
+        %131 = OpLabel
+        %132 = OpPhi %7 %35 %37 %39 %38
+               OpReturnValue %132
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, NestedLoops) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 2
+         %11 = OpConstant %5 1
+         %12 = OpConstant %5 3
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %5 None %6
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %17 %16 None
+               OpBranchConditional %8 %18 %16
+         %18 = OpLabel
+               OpLoopMerge %19 %20 None
+               OpBranchConditional %8 %19 %21
+         %19 = OpLabel
+               OpBranch %17
+         %21 = OpLabel
+               OpReturnValue %12
+         %17 = OpLabel
+               OpBranch %22
+         %20 = OpLabel
+               OpBranch %18
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranchConditional %8 %26 %23
+         %26 = OpLabel
+               OpSelectionMerge %27 None
+               OpBranchConditional %9 %28 %27
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpLoopMerge %30 %29 None
+               OpBranchConditional %8 %30 %29
+         %30 = OpLabel
+               OpLoopMerge %31 %32 None
+               OpBranch %33
+         %33 = OpLabel
+               OpBranchConditional %9 %34 %31
+         %34 = OpLabel
+               OpReturnValue %10
+         %32 = OpLabel
+               OpBranch %30
+         %31 = OpLabel
+         %35 = OpPhi %5 %11 %33
+         %36 = OpPhi %5 %10 %33
+               OpBranch %37
+         %37 = OpLabel
+               OpReturnValue %35
+         %27 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+               OpBranch %38
+         %38 = OpLabel
+               OpReturnValue %12
+               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);
+
+  auto transformation = TransformationMergeFunctionReturns(
+      14, 100, 101, 102, 11,
+      {{MakeReturnMergingInfo(19, 103, 104, {{}}),
+        MakeReturnMergingInfo(17, 105, 106, {{}}),
+        MakeReturnMergingInfo(31, 107, 108, {{{35, 10}, {36, 12}}}),
+        MakeReturnMergingInfo(23, 109, 110, {})}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 2
+         %11 = OpConstant %5 1
+         %12 = OpConstant %5 3
+          %2 = OpFunction %3 None %4
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %14 = OpFunction %5 None %6
+         %15 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %8 %16 %100
+         %16 = OpLabel
+               OpLoopMerge %17 %16 None
+               OpBranchConditional %8 %18 %16
+         %18 = OpLabel
+               OpLoopMerge %19 %20 None
+               OpBranchConditional %8 %19 %21
+         %19 = OpLabel
+        %103 = OpPhi %7 %8 %21 %9 %18
+        %104 = OpPhi %5 %12 %21 %11 %18
+               OpBranch %17
+         %21 = OpLabel
+               OpBranch %19
+         %17 = OpLabel
+        %105 = OpPhi %7 %103 %19
+        %106 = OpPhi %5 %104 %19
+               OpBranchConditional %105 %101 %22
+         %20 = OpLabel
+               OpBranch %18
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranchConditional %8 %26 %23
+         %26 = OpLabel
+               OpSelectionMerge %27 None
+               OpBranchConditional %9 %28 %27
+         %28 = OpLabel
+               OpBranch %29
+         %29 = OpLabel
+               OpLoopMerge %30 %29 None
+               OpBranchConditional %8 %30 %29
+         %30 = OpLabel
+               OpLoopMerge %31 %32 None
+               OpBranch %33
+         %33 = OpLabel
+               OpBranchConditional %9 %34 %31
+         %34 = OpLabel
+               OpBranch %31
+         %32 = OpLabel
+               OpBranch %30
+         %31 = OpLabel
+        %107 = OpPhi %7 %8 %34 %9 %33
+        %108 = OpPhi %5 %10 %34 %11 %33
+         %35 = OpPhi %5 %11 %33 %10 %34
+         %36 = OpPhi %5 %10 %33 %12 %34
+               OpBranchConditional %107 %23 %37
+         %37 = OpLabel
+               OpBranch %23
+         %27 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranch %22
+         %23 = OpLabel
+        %109 = OpPhi %7 %107 %31 %8 %37 %9 %25
+        %110 = OpPhi %5 %108 %31 %35 %37 %11 %25
+               OpBranchConditional %109 %101 %38
+         %38 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %5 %106 %17 %110 %23 %12 %38
+               OpReturnValue %102
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, OverflowIds) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %5 None %6
+         %13 = OpLabel
+               OpBranch %14
+         %14 = OpLabel
+         %15 = OpIAdd %5 %10 %10
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+               OpBranchConditional %8 %19 %16
+         %19 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %9 %21 %20
+         %21 = OpLabel
+               OpReturnValue %10
+         %20 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranchConditional %8 %14 %16
+         %16 = OpLabel
+         %22 = OpPhi %5 %15 %17 %10 %18
+               OpBranch %23
+         %23 = OpLabel
+               OpReturnValue %22
+               OpFunctionEnd
+         %24 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %26
+         %26 = OpLabel
+               OpLoopMerge %27 %28 None
+               OpBranch %29
+         %29 = OpLabel
+               OpBranchConditional %8 %30 %27
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %9 %32 %31
+         %32 = OpLabel
+               OpReturn
+         %31 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %26
+         %27 = OpLabel
+         %33 = OpPhi %5 %10 %29
+               OpBranch %34
+         %34 = 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);
+
+  auto overflow_ids_unique_ptr = MakeUnique<CounterOverflowIdSource>(1000);
+  auto overflow_ids_ptr = overflow_ids_unique_ptr.get();
+  TransformationContext transformation_context_with_overflow_ids(
+      MakeUnique<FactManager>(context.get()), validator_options,
+      std::move(overflow_ids_unique_ptr));
+
+  // No mapping from merge block %16 to fresh ids is given, so overflow ids are
+  // needed.
+  auto transformation1 =
+      TransformationMergeFunctionReturns(12, 100, 101, 102, 10, {{}});
+
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      transformation1.IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  ASSERT_TRUE(transformation1.IsApplicable(
+      context.get(), transformation_context_with_overflow_ids));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context_with_overflow_ids,
+                        overflow_ids_ptr->GetIssuedOverflowIds());
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // No mapping from merge block %27 to fresh ids is given, so overflow ids are
+  // needed.
+  auto transformation2 =
+      TransformationMergeFunctionReturns(24, 110, 111, 0, 0, {{}});
+
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      transformation2.IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  ASSERT_TRUE(transformation2.IsApplicable(
+      context.get(), transformation_context_with_overflow_ids));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context_with_overflow_ids, {1002});
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpConstantFalse %7
+         %10 = OpConstant %5 1
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %5 None %6
+         %13 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %8 %14 %100
+         %14 = OpLabel
+         %15 = OpIAdd %5 %10 %10
+               OpLoopMerge %16 %17 None
+               OpBranch %18
+         %18 = OpLabel
+               OpBranchConditional %8 %19 %16
+         %19 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %9 %21 %20
+         %21 = OpLabel
+               OpBranch %16
+         %20 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpBranchConditional %8 %14 %16
+         %16 = OpLabel
+       %1000 = OpPhi %7 %8 %21 %9 %17 %9 %18
+       %1001 = OpPhi %5 %10 %21 %10 %17 %10 %18
+         %22 = OpPhi %5 %15 %17 %10 %18 %10 %21
+               OpBranchConditional %1000 %101 %23
+         %23 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+        %102 = OpPhi %5 %1001 %16 %22 %23
+               OpReturnValue %102
+               OpFunctionEnd
+         %24 = OpFunction %3 None %4
+         %25 = OpLabel
+               OpBranch %110
+        %110 = OpLabel
+               OpLoopMerge %111 %110 None
+               OpBranchConditional %8 %26 %110
+         %26 = OpLabel
+               OpLoopMerge %27 %28 None
+               OpBranch %29
+         %29 = OpLabel
+               OpBranchConditional %8 %30 %27
+         %30 = OpLabel
+               OpSelectionMerge %31 None
+               OpBranchConditional %9 %32 %31
+         %32 = OpLabel
+               OpBranch %27
+         %31 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %26
+         %27 = OpLabel
+       %1002 = OpPhi %7 %8 %32 %9 %29
+         %33 = OpPhi %5 %10 %29 %10 %32
+               OpBranchConditional %1002 %111 %34
+         %34 = OpLabel
+               OpBranch %111
+        %111 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, MissingIdsForOpPhi) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpTypeFunction %3 %8
+         %10 = OpTypeFloat 32
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %3 None %9
+         %13 = OpFunctionParameter %8
+         %14 = OpLabel
+         %15 = OpConvertSToF %10 %13
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %17 %18 None
+               OpBranch %19
+         %19 = OpLabel
+               OpBranchConditional %6 %20 %17
+         %20 = OpLabel
+               OpSelectionMerge %21 None
+               OpBranchConditional %7 %22 %21
+         %22 = OpLabel
+               OpReturn
+         %21 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+         %23 = OpPhi %8 %13 %19
+         %24 = OpPhi %10 %15 %19
+         %25 = OpPhi %5 %6 %19
+               OpBranch %26
+         %26 = 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);
+
+  // This test checks whether the transformation is able to find suitable ids
+  // to use in existing OpPhi instructions if they are not provided in the
+  // corresponding mapping.
+
+  auto transformation = TransformationMergeFunctionReturns(
+      12, 101, 102, 0, 0,
+      {{MakeReturnMergingInfo(17, 103, 0, {{{25, 7}, {35, 8}}})}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpTypeFunction %3 %8
+         %10 = OpTypeFloat 32
+          %2 = OpFunction %3 None %4
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %3 None %9
+         %13 = OpFunctionParameter %8
+         %14 = OpLabel
+         %15 = OpConvertSToF %10 %13
+               OpBranch %101
+        %101 = OpLabel
+               OpLoopMerge %102 %101 None
+               OpBranchConditional %6 %16 %101
+         %16 = OpLabel
+               OpLoopMerge %17 %18 None
+               OpBranch %19
+         %19 = OpLabel
+               OpBranchConditional %6 %20 %17
+         %20 = OpLabel
+               OpSelectionMerge %21 None
+               OpBranchConditional %7 %22 %21
+         %22 = OpLabel
+               OpBranch %17
+         %21 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+               OpBranch %16
+         %17 = OpLabel
+        %103 = OpPhi %5 %6 %22 %7 %19
+         %23 = OpPhi %8 %13 %19 %13 %22
+         %24 = OpPhi %10 %15 %19 %15 %22
+         %25 = OpPhi %5 %6 %19 %7 %22
+               OpBranchConditional %103 %102 %26
+         %26 = OpLabel
+               OpBranch %102
+        %102 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules1) {
+  // An id defined in a loop is used in the corresponding merge block. After the
+  // transformation, the id will not dominate the merge block anymore. This is
+  // only OK if the use is inside an OpPhi instruction. (Note that there is also
+  // another condition for this transformation that forbids non-OpPhi
+  // instructions in relevant merge blocks, but that case is also considered
+  // here for completeness).
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+         %16 = OpCopyObject %5 %15
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %7 %24 %25
+         %25 = OpLabel
+               OpReturn
+         %24 = OpLabel
+         %26 = OpCopyObject %5 %7
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %7 %20 %21
+         %21 = OpLabel
+         %27 = OpPhi %5 %26 %22
+               OpBranch %28
+         %28 = 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);
+
+  // In function %2, the definition of id %15 will not dominate its use in
+  // instruction %16 (inside merge block %10) after a new branch from return
+  // block %14 is added.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          2, 100, 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, {{}})}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+         %16 = OpCopyObject %5 %15
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %6 %20 %100
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %7 %24 %25
+         %25 = OpLabel
+               OpBranch %21
+         %24 = OpLabel
+         %26 = OpCopyObject %5 %7
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %7 %20 %21
+         %21 = OpLabel
+        %102 = OpPhi %5 %6 %25 %7 %22
+         %27 = OpPhi %5 %26 %22 %6 %25
+               OpBranchConditional %102 %101 %28
+         %28 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules2) {
+  // An id defined in a loop is used after the corresponding merge block. After
+  // the transformation, the id will not dominate its use anymore, regardless of
+  // the kind of instruction in which it is used.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpCopyObject %5 %15
+               OpReturn
+               OpFunctionEnd
+         %18 = OpFunction %3 None %4
+         %19 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %21 %22 None
+               OpBranch %23
+         %23 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %7 %24 %25
+         %25 = OpLabel
+               OpReturn
+         %24 = OpLabel
+         %26 = OpCopyObject %5 %7
+               OpBranch %22
+         %22 = OpLabel
+               OpBranchConditional %7 %20 %21
+         %21 = OpLabel
+               OpBranch %27
+         %27 = OpLabel
+         %28 = OpPhi %5 %26 %21
+               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);
+
+  // In function %2, the definition of id %15 will not dominate its use in
+  // instruction %17 (inside block %16) after a new branch from return
+  // block %14 to merge block %10 is added.
+  ASSERT_FALSE(
+      TransformationMergeFunctionReturns(
+          2, 100, 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));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules3) {
+  // An id defined in a loop is used inside the loop.
+  // Changes to the predecessors of the merge block do not affect the validity
+  // of the uses of such id.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpReturn
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+         %18 = OpPhi %5 %15 %13
+         %19 = OpCopyObject %5 %15
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+               OpBranch %20
+         %20 = 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);
+
+  // In function %2, the definition of id %15 will still dominate its use in
+  // instructions %18 and %19 after the transformation is applied, because the
+  // 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, {{}})}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %6 %9 %100
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+               OpSelectionMerge %13 None
+               OpBranchConditional %7 %13 %14
+         %14 = OpLabel
+               OpBranch %10
+         %13 = OpLabel
+         %15 = OpCopyObject %5 %7
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+         %18 = OpPhi %5 %15 %13
+         %19 = OpCopyObject %5 %15
+               OpBranch %16
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+        %102 = OpPhi %5 %6 %14 %7 %11
+               OpBranchConditional %102 %101 %20
+         %20 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, RespectDominanceRules4) {
+  // An id defined in a loop, which contain 2 return statements, is used after
+  // the loop. We can only apply the transformation if the id dominates all of
+  // the return blocks.
+
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpCopyObject %5 %7
+               OpSelectionMerge %14 None
+               OpBranchConditional %7 %14 %15
+         %15 = OpLabel
+               OpReturn
+         %14 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+               OpReturn
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+               OpBranch %18
+         %18 = OpLabel
+         %19 = OpCopyObject %5 %13
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %3 None %4
+         %21 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %7 %26 %27
+         %27 = OpLabel
+               OpReturn
+         %26 = OpLabel
+         %28 = OpCopyObject %5 %7
+               OpSelectionMerge %29 None
+               OpBranchConditional %7 %29 %30
+         %30 = OpLabel
+               OpReturn
+         %29 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranchConditional %7 %22 %23
+         %23 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpCopyObject %5 %28
+               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);
+
+  // In function %2, the definition of id %13 will still dominate its use in
+  // 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, {{}})}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // 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));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %6 %9 %100
+          %9 = OpLabel
+               OpLoopMerge %10 %11 None
+               OpBranch %12
+         %12 = OpLabel
+         %13 = OpCopyObject %5 %7
+               OpSelectionMerge %14 None
+               OpBranchConditional %7 %14 %15
+         %15 = OpLabel
+               OpBranch %10
+         %14 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %7 %16 %17
+         %17 = OpLabel
+               OpBranch %10
+         %16 = OpLabel
+               OpBranch %11
+         %11 = OpLabel
+               OpBranchConditional %7 %9 %10
+         %10 = OpLabel
+        %102 = OpPhi %5 %6 %15 %6 %17 %7 %11
+               OpBranchConditional %102 %101 %18
+         %18 = OpLabel
+         %19 = OpCopyObject %5 %13
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %3 None %4
+         %21 = OpLabel
+               OpBranch %22
+         %22 = OpLabel
+               OpLoopMerge %23 %24 None
+               OpBranch %25
+         %25 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %7 %26 %27
+         %27 = OpLabel
+               OpReturn
+         %26 = OpLabel
+         %28 = OpCopyObject %5 %7
+               OpSelectionMerge %29 None
+               OpBranchConditional %7 %29 %30
+         %30 = OpLabel
+               OpReturn
+         %29 = OpLabel
+               OpBranch %24
+         %24 = OpLabel
+               OpBranchConditional %7 %22 %23
+         %23 = OpLabel
+               OpBranch %31
+         %31 = OpLabel
+         %32 = OpCopyObject %5 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMergeFunctionReturnsTest, OpPhiAfterFirstBlock) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+         %10 = OpPhi %5 %6 %8
+               OpSelectionMerge %11 None
+               OpBranchConditional %6 %12 %11
+         %12 = OpLabel
+               OpReturn
+         %11 = 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);
+
+  auto transformation =
+      TransformationMergeFunctionReturns(2, 100, 101, 0, 0, {});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Ensure that all input operands of OpBranchConditional instructions have
+  // the right operand type.
+  context->module()->ForEachInst([](opt::Instruction* inst) {
+    if (inst->opcode() == SpvOpBranchConditional) {
+      ASSERT_EQ(inst->GetInOperand(0).type, SPV_OPERAND_TYPE_ID);
+      ASSERT_EQ(inst->GetInOperand(1).type, SPV_OPERAND_TYPE_ID);
+      ASSERT_EQ(inst->GetInOperand(2).type, SPV_OPERAND_TYPE_ID);
+    }
+  });
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %2 = OpFunction %3 None %4
+          %8 = OpLabel
+               OpBranch %100
+        %100 = OpLabel
+               OpLoopMerge %101 %100 None
+               OpBranchConditional %6 %9 %100
+          %9 = OpLabel
+         %10 = OpPhi %5 %6 %100
+               OpSelectionMerge %11 None
+               OpBranchConditional %6 %12 %11
+         %12 = OpLabel
+               OpBranch %101
+         %11 = OpLabel
+               OpBranch %101
+        %101 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
\ No newline at end of file
diff --git a/test/fuzz/transformation_move_block_down_test.cpp b/test/fuzz/transformation_move_block_down_test.cpp
index 662e88c..e8d2588 100644
--- a/test/fuzz/transformation_move_block_down_test.cpp
+++ b/test/fuzz/transformation_move_block_down_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_move_block_down.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -52,11 +55,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto transformation = TransformationMoveBlockDown(11);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
@@ -93,11 +94,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto transformation = TransformationMoveBlockDown(5);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
@@ -136,11 +135,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto transformation = TransformationMoveBlockDown(100);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
@@ -183,11 +180,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto transformation = TransformationMoveBlockDown(12);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
@@ -292,11 +287,9 @@
   const auto context =
       BuildModule(env, consumer, before_transformation, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // The block ids are: 5 14 20 23 21 25 29 32 30 15
   // We make a transformation to move each of them down, plus a transformation
   // to move a non-block, 27, down.
@@ -341,8 +334,9 @@
 
   // Let's bubble 20 all the way down.
 
-  move_down_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_20, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 23 20 21 25 29 32 30 15
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -359,8 +353,9 @@
   ASSERT_FALSE(
       move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_20, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 23 21 20 25 29 32 30 15
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -377,8 +372,9 @@
   ASSERT_FALSE(
       move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_20, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 23 21 25 20 29 32 30 15
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -394,8 +390,9 @@
   ASSERT_FALSE(
       move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_20, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 23 21 25 29 20 32 30 15
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -412,8 +409,9 @@
   ASSERT_FALSE(
       move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_20, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 23 21 25 29 32 20 30 15
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -430,8 +428,9 @@
   ASSERT_FALSE(
       move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_20, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 23 21 25 29 32 30 20 15
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -448,8 +447,9 @@
   ASSERT_FALSE(
       move_down_15.IsApplicable(context.get(), transformation_context));
 
-  move_down_20.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_20, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_bubbling_20_down = R"(
                OpCapability Shader
@@ -538,8 +538,9 @@
   ASSERT_FALSE(
       move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_23.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_23, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 21 23 25 29 32 30 15 20
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -556,8 +557,9 @@
   ASSERT_FALSE(
       move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_23.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_23, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 21 25 23 29 32 30 15 20
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -573,8 +575,9 @@
   ASSERT_FALSE(
       move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_21.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_21, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Current ordering: 5 14 25 21 23 29 32 30 15 20
   ASSERT_FALSE(move_down_5.IsApplicable(context.get(), transformation_context));
@@ -589,8 +592,9 @@
   ASSERT_FALSE(
       move_down_20.IsApplicable(context.get(), transformation_context));
 
-  move_down_14.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(move_down_14, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_more_shuffling = R"(
                OpCapability Shader
@@ -707,13 +711,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto transformation = TransformationMoveBlockDown(6);
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
diff --git a/test/fuzz/transformation_move_instruction_down_test.cpp b/test/fuzz/transformation_move_instruction_down_test.cpp
new file mode 100644
index 0000000..45dde7d
--- /dev/null
+++ b/test/fuzz/transformation_move_instruction_down_test.cpp
@@ -0,0 +1,720 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_move_instruction_down.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(TransformationMoveInstructionDownTest, 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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %9 = OpConstant %6 0
+         %16 = OpTypeBool
+         %17 = OpConstantFalse %16
+         %20 = OpUndef %6
+         %13 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %13 Function
+         %10 = OpIAdd %6 %9 %9
+         %11 = OpISub %6 %9 %10
+               OpStore %12 %10
+         %14 = OpLoad %6 %12
+         %15 = OpIMul %6 %9 %14
+               OpSelectionMerge %19 None
+               OpBranchConditional %17 %18 %19
+         %18 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+         %42 = OpFunctionCall %2 %40
+         %22 = OpIAdd %6 %15 %15
+         %21 = OpIAdd %6 %15 %15
+               OpReturn
+               OpFunctionEnd
+         %40 = OpFunction %2 None %3
+         %41 = 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);
+  // Instruction descriptor is invalid.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(30, SpvOpNop, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Opcode is not supported.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(5, SpvOpLabel, 0))
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(12, SpvOpVariable, 0))
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(42, SpvOpFunctionCall, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Can't move the last instruction in the block.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(15, SpvOpBranchConditional, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Can't move the instruction if the next instruction is the last one in the
+  // block.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(21, SpvOpIAdd, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Can't insert instruction's opcode after its successor.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(15, SpvOpIMul, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Instruction's successor depends on the instruction.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(10, SpvOpIAdd, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(11, SpvOpISub, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(22, SpvOpIAdd, 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
+          %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
+          %9 = OpConstant %6 0
+         %16 = OpTypeBool
+         %17 = OpConstantFalse %16
+         %20 = OpUndef %6
+         %13 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %12 = OpVariable %13 Function
+         %10 = OpIAdd %6 %9 %9
+               OpStore %12 %10
+         %11 = OpISub %6 %9 %10
+         %14 = OpLoad %6 %12
+         %15 = OpIMul %6 %9 %14
+               OpSelectionMerge %19 None
+               OpBranchConditional %17 %18 %19
+         %18 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+         %42 = OpFunctionCall %2 %40
+         %21 = OpIAdd %6 %15 %15
+         %22 = OpIAdd %6 %15 %15
+               OpReturn
+               OpFunctionEnd
+         %40 = OpFunction %2 None %3
+         %41 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMoveInstructionDownTest, HandlesUnsupportedInstructions) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 16 1 1
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 2
+         %20 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %21 = OpVariable %20 Function %7
+
+          ; can swap simple and not supported instructions
+          %8 = OpCopyObject %6 %7
+          %9 = OpFunctionCall %2 %12
+
+         ; cannot swap memory and not supported instruction
+         %22 = OpLoad %6 %21
+         %23 = OpFunctionCall %2 %12
+
+         ; cannot swap barrier and not supported instruction
+               OpMemoryBarrier %7 %7
+         %24 = OpFunctionCall %2 %12
+
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = 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);
+  // Swap memory instruction with an unsupported one.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(22, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Swap memory barrier with an unsupported one.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(23, SpvOpMemoryBarrier, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Swap simple instruction with an unsupported one.
+  TransformationMoveInstructionDown transformation(
+      MakeInstructionDescriptor(8, SpvOpCopyObject, 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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 16 1 1
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 2
+         %20 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %21 = OpVariable %20 Function %7
+
+          ; can swap simple and not supported instructions
+          %9 = OpFunctionCall %2 %12
+          %8 = OpCopyObject %6 %7
+
+         ; cannot swap memory and not supported instruction
+         %22 = OpLoad %6 %21
+         %23 = OpFunctionCall %2 %12
+
+         ; cannot swap barrier and not supported instruction
+               OpMemoryBarrier %7 %7
+         %24 = OpFunctionCall %2 %12
+
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMoveInstructionDownTest, HandlesBarrierInstructions) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 16 1 1
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 2
+         %20 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %21 = OpVariable %20 Function %7
+
+          ; cannot swap two barrier instructions
+               OpMemoryBarrier %7 %7
+               OpMemoryBarrier %7 %7
+
+         ; cannot swap barrier and memory instructions
+               OpMemoryBarrier %7 %7
+         %22 = OpLoad %6 %21
+               OpMemoryBarrier %7 %7
+
+         ; can swap barrier and simple instructions
+         %23 = OpCopyObject %6 %7
+               OpMemoryBarrier %7 %7
+
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = 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);
+  // Swap two barrier instructions.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Swap barrier and memory instructions.
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 2))
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationMoveInstructionDown(
+                   MakeInstructionDescriptor(22, SpvOpLoad, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Swap barrier and simple instructions.
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(23, SpvOpCopyObject, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(22, SpvOpMemoryBarrier, 1));
+    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(IsEqual(env, shader, context.get()));
+}
+
+TEST(TransformationMoveInstructionDownTest, HandlesSimpleInstructions) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 16 1 1
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 2
+         %20 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %21 = OpVariable %20 Function %7
+
+         ; can swap simple and barrier instructions
+         %40 = OpCopyObject %6 %7
+               OpMemoryBarrier %7 %7
+
+         ; can swap simple and memory instructions
+         %41 = OpCopyObject %6 %7
+         %22 = OpLoad %6 %21
+
+         ; can swap two simple instructions
+         %23 = OpCopyObject %6 %7
+         %42 = OpCopyObject %6 %7
+
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = 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);
+  // Swap simple and barrier instructions.
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(40, SpvOpCopyObject, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  // Swap simple and memory instructions.
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(41, SpvOpCopyObject, 0));
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(22, 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));
+  }
+
+  // Swap two simple instructions.
+  {
+    TransformationMoveInstructionDown transformation(
+        MakeInstructionDescriptor(23, SpvOpCopyObject, 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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 16 1 1
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 2
+         %20 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %21 = OpVariable %20 Function %7
+
+         ; can swap simple and barrier instructions
+         %40 = OpCopyObject %6 %7
+               OpMemoryBarrier %7 %7
+
+         ; can swap simple and memory instructions
+         %41 = OpCopyObject %6 %7
+         %22 = OpLoad %6 %21
+
+         ; can swap two simple instructions
+         %42 = OpCopyObject %6 %7
+         %23 = OpCopyObject %6 %7
+
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %2 None %3
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMoveInstructionDownTest, HandlesMemoryInstructions) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %4 "main"
+               OpExecutionMode %4 LocalSize 16 1 1
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpConstant %6 2
+         %20 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %21 = OpVariable %20 Function %7
+         %22 = OpVariable %20 Function %7
+
+         ; swap R and R instructions
+         %23 = OpLoad %6 %21
+         %24 = OpLoad %6 %22
+
+         ; swap R and RW instructions
+
+           ; can't swap
+         %25 = OpLoad %6 %21
+               OpCopyMemory %21 %22
+
+           ; can swap
+         %26 = OpLoad %6 %21
+               OpCopyMemory %22 %21
+
+         %27 = OpLoad %6 %22
+               OpCopyMemory %21 %22
+
+         %28 = OpLoad %6 %22
+               OpCopyMemory %22 %21
+
+         ; swap R and W instructions
+
+           ; can't swap
+         %29 = OpLoad %6 %21
+               OpStore %21 %7
+
+           ; can swap
+         %30 = OpLoad %6 %22
+               OpStore %21 %7
+
+         %31 = OpLoad %6 %21
+               OpStore %22 %7
+
+         %32 = OpLoad %6 %22
+               OpStore %22 %7
+
+         ; swap RW and RW instructions
+
+           ; can't swap
+               OpCopyMemory %21 %21
+               OpCopyMemory %21 %21
+
+               OpCopyMemory %21 %22
+               OpCopyMemory %21 %21
+
+               OpCopyMemory %21 %21
+               OpCopyMemory %21 %22
+
+           ; can swap
+               OpCopyMemory %22 %21
+               OpCopyMemory %21 %22
+
+               OpCopyMemory %22 %21
+               OpCopyMemory %22 %21
+
+               OpCopyMemory %21 %22
+               OpCopyMemory %21 %22
+
+         ; swap RW and W instructions
+
+           ; can't swap
+               OpCopyMemory %21 %21
+               OpStore %21 %7
+
+               OpStore %21 %7
+               OpCopyMemory %21 %21
+
+           ; can swap
+               OpCopyMemory %22 %21
+               OpStore %21 %7
+
+               OpCopyMemory %21 %22
+               OpStore %21 %7
+
+               OpCopyMemory %21 %21
+               OpStore %22 %7
+
+         ; swap W and W instructions
+
+           ; can't swap
+               OpStore %21 %7
+               OpStore %21 %7
+
+           ; can swap
+               OpStore %22 %7
+               OpStore %21 %7
+
+               OpStore %22 %7
+               OpStore %22 %7
+
+               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(
+      22);
+
+  // Invalid swaps.
+
+  protobufs::InstructionDescriptor invalid_swaps[] = {
+      // R and RW
+      MakeInstructionDescriptor(25, SpvOpLoad, 0),
+
+      // R and W
+      MakeInstructionDescriptor(29, SpvOpLoad, 0),
+
+      // RW and RW
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 0),
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 2),
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 4),
+
+      // RW and W
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 12),
+      MakeInstructionDescriptor(32, SpvOpStore, 1),
+
+      // W and W
+      MakeInstructionDescriptor(32, SpvOpStore, 6),
+  };
+
+  for (const auto& descriptor : invalid_swaps) {
+    ASSERT_FALSE(TransformationMoveInstructionDown(descriptor)
+                     .IsApplicable(context.get(), transformation_context));
+  }
+
+  // Valid swaps.
+  protobufs::InstructionDescriptor valid_swaps[] = {
+      // R and R
+      MakeInstructionDescriptor(23, SpvOpLoad, 0),
+      MakeInstructionDescriptor(24, SpvOpLoad, 0),
+
+      // R and RW
+      MakeInstructionDescriptor(26, SpvOpLoad, 0),
+      MakeInstructionDescriptor(25, SpvOpCopyMemory, 1),
+
+      MakeInstructionDescriptor(27, SpvOpLoad, 0),
+      MakeInstructionDescriptor(26, SpvOpCopyMemory, 1),
+
+      MakeInstructionDescriptor(28, SpvOpLoad, 0),
+      MakeInstructionDescriptor(27, SpvOpCopyMemory, 1),
+
+      // R and W
+      MakeInstructionDescriptor(30, SpvOpLoad, 0),
+      MakeInstructionDescriptor(29, SpvOpStore, 1),
+
+      MakeInstructionDescriptor(31, SpvOpLoad, 0),
+      MakeInstructionDescriptor(30, SpvOpStore, 1),
+
+      MakeInstructionDescriptor(32, SpvOpLoad, 0),
+      MakeInstructionDescriptor(31, SpvOpStore, 1),
+
+      // RW and RW
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 6),
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 6),
+
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 8),
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 8),
+
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 10),
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 10),
+
+      // RW and W
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 14),
+      MakeInstructionDescriptor(32, SpvOpStore, 3),
+
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 15),
+      MakeInstructionDescriptor(32, SpvOpStore, 4),
+
+      MakeInstructionDescriptor(32, SpvOpCopyMemory, 16),
+      MakeInstructionDescriptor(32, SpvOpStore, 5),
+
+      // W and W
+      MakeInstructionDescriptor(32, SpvOpStore, 8),
+      MakeInstructionDescriptor(32, SpvOpStore, 8),
+
+      MakeInstructionDescriptor(32, SpvOpStore, 10),
+      MakeInstructionDescriptor(32, SpvOpStore, 10),
+  };
+
+  for (const auto& descriptor : valid_swaps) {
+    TransformationMoveInstructionDown transformation(descriptor);
+    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(IsEqual(env, shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_mutate_pointer_test.cpp b/test/fuzz/transformation_mutate_pointer_test.cpp
new file mode 100644
index 0000000..ae42310
--- /dev/null
+++ b/test/fuzz/transformation_mutate_pointer_test.cpp
@@ -0,0 +1,322 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_mutate_pointer.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(TransformationMutatePointerTest, 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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFloat 32
+         %34 = OpConstant %7 0
+         %36 = OpConstant %6 0
+         %14 = OpTypeVector %7 3
+         %35 = OpConstantComposite %14 %34 %34 %34
+         %15 = OpTypeMatrix %14 2
+          %8 = OpConstant %6 5
+          %9 = OpTypeArray %7 %8
+         %37 = OpConstantComposite %9 %34 %34 %34 %34 %34
+         %11 = OpTypeStruct
+         %38 = OpConstantComposite %11
+         %39 = OpConstantComposite %15 %35 %35
+         %31 = OpTypePointer Function %14
+         %10 = OpTypeStruct %7 %6 %9 %11 %15 %14
+         %40 = OpConstantComposite %10 %34 %36 %37 %38 %39 %35
+         %13 = OpTypePointer Function %10
+         %16 = OpTypePointer Private %10
+         %17 = OpTypePointer Workgroup %10
+         %18 = OpTypeStruct %16
+         %19 = OpTypePointer Private %18
+         %20 = OpVariable %16 Private
+         %21 = OpVariable %17 Workgroup
+         %22 = OpVariable %19 Private
+         %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
+               OpReturn
+               OpFunctionEnd
+         %28 = OpFunction %2 None %27
+         %29 = OpFunctionParameter %13
+         %30 = OpLabel
+         %25 = OpVariable %13 Function
+         %26 = OpAccessChain %31 %25 %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));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(35);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(39);
+
+  const auto insert_before = MakeInstructionDescriptor(26, SpvOpReturn, 0);
+
+  // 20 is not a fresh id.
+  ASSERT_FALSE(TransformationMutatePointer(20, 20, insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // |insert_before| instruction descriptor is invalid.
+  ASSERT_FALSE(TransformationMutatePointer(
+                   20, 70, MakeInstructionDescriptor(26, SpvOpStore, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Can't insert OpLoad before OpVariable.
+  ASSERT_FALSE(TransformationMutatePointer(
+                   20, 70, MakeInstructionDescriptor(26, SpvOpVariable, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  // |pointer_id| doesn't exist in the module.
+  ASSERT_FALSE(TransformationMutatePointer(70, 70, insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // |pointer_id| doesn't have a type id.
+  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));
+
+  // |pointer_id| is not a pointer instruction.
+  ASSERT_FALSE(TransformationMutatePointer(8, 70, insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // |pointer_id| has invalid storage class
+  ASSERT_FALSE(TransformationMutatePointer(24, 70, insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // |pointer_id|'s pointee contains non-scalar and non-composite constituents.
+  ASSERT_FALSE(TransformationMutatePointer(22, 70, insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // There is no irrelevant zero constant to insert into the |pointer_id|.
+  ASSERT_FALSE(TransformationMutatePointer(20, 70, insert_before)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // |pointer_id| is not available before |insert_before|.
+  ASSERT_FALSE(TransformationMutatePointer(
+                   26, 70, MakeInstructionDescriptor(26, SpvOpAccessChain, 0))
+                   .IsApplicable(context.get(), transformation_context));
+
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(40);
+
+  uint32_t fresh_id = 70;
+  uint32_t pointer_ids[] = {
+      20,  // Mutate Private variable.
+      21,  // Mutate Workgroup variable.
+      25,  // Mutate Function variable.
+      29,  // Mutate function parameter.
+      26,  // Mutate OpAccessChain.
+  };
+
+  for (auto pointer_id : pointer_ids) {
+    TransformationMutatePointer transformation(pointer_id, fresh_id++,
+                                               insert_before);
+    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
+          %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
+          %7 = OpTypeFloat 32
+         %34 = OpConstant %7 0
+         %36 = OpConstant %6 0
+         %14 = OpTypeVector %7 3
+         %35 = OpConstantComposite %14 %34 %34 %34
+         %15 = OpTypeMatrix %14 2
+          %8 = OpConstant %6 5
+          %9 = OpTypeArray %7 %8
+         %37 = OpConstantComposite %9 %34 %34 %34 %34 %34
+         %11 = OpTypeStruct
+         %38 = OpConstantComposite %11
+         %39 = OpConstantComposite %15 %35 %35
+         %31 = OpTypePointer Function %14
+         %10 = OpTypeStruct %7 %6 %9 %11 %15 %14
+         %40 = OpConstantComposite %10 %34 %36 %37 %38 %39 %35
+         %13 = OpTypePointer Function %10
+         %16 = OpTypePointer Private %10
+         %17 = OpTypePointer Workgroup %10
+         %18 = OpTypeStruct %16
+         %19 = OpTypePointer Private %18
+         %20 = OpVariable %16 Private
+         %21 = OpVariable %17 Workgroup
+         %22 = OpVariable %19 Private
+         %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
+               OpReturn
+               OpFunctionEnd
+         %28 = OpFunction %2 None %27
+         %29 = OpFunctionParameter %13
+         %30 = OpLabel
+         %25 = OpVariable %13 Function
+         %26 = OpAccessChain %31 %25 %8
+
+         ; modified Private variable
+         %70 = OpLoad %10 %20
+               OpStore %20 %40
+               OpStore %20 %70
+
+         ; modified Workgroup variable
+         %71 = OpLoad %10 %21
+               OpStore %21 %40
+               OpStore %21 %71
+
+         ; modified Function variable
+         %72 = OpLoad %10 %25
+               OpStore %25 %40
+               OpStore %25 %72
+
+         ; modified function parameter
+         %73 = OpLoad %10 %29
+               OpStore %29 %40
+               OpStore %29 %73
+
+         ; modified OpAccessChain
+         %74 = OpLoad %14 %26
+               OpStore %26 %35
+               OpStore %26 %74
+
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMutatePointerTest, HandlesUnreachableBlocks) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 0
+          %8 = OpTypePointer Function %6
+         %11 = OpTypePointer Private %6
+         %12 = OpVariable %11 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+               OpReturn
+         %10 = 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);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(7);
+
+  ASSERT_FALSE(
+      context->GetDominatorAnalysis(context->GetFunction(4))->IsReachable(10));
+
+  const auto insert_before = MakeInstructionDescriptor(10, SpvOpReturn, 0);
+
+  // Can mutate a global variable in an unreachable block.
+  TransformationMutatePointer transformation(12, 50, insert_before);
+  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
+          %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
+          %7 = OpConstant %6 0
+          %8 = OpTypePointer Function %6
+         %11 = OpTypePointer Private %6
+         %12 = OpVariable %11 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+               OpReturn
+         %10 = OpLabel
+         %50 = OpLoad %6 %12
+               OpStore %12 %7
+               OpStore %12 %50
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_outline_function_test.cpp b/test/fuzz/transformation_outline_function_test.cpp
index ed4fd15..6c0bff4 100644
--- a/test/fuzz/transformation_outline_function_test.cpp
+++ b/test/fuzz/transformation_outline_function_test.cpp
@@ -13,6 +13,10 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_outline_function.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/counter_overflow_id_source.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -41,20 +45,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               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_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -106,13 +109,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               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, {}, {});
@@ -163,20 +164,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 13, /* not relevant */
                                                200, 100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -252,20 +252,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 6, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -330,19 +329,18 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 6, 99, 100, 101, 102, 103,
                                                105, {}, {{9, 104}});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -429,20 +427,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       6, 80, 100, 101, 102, 103, 104, 105, {},
       {{15, 106}, {9, 107}, {7, 108}, {8, 109}});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -529,19 +526,18 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 6, 100, 101, 102, 103, 104,
                                                105, {{7, 106}}, {});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -607,19 +603,18 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 6, 100, 101, 102, 103, 104,
                                                105, {{13, 106}}, {});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -695,19 +690,18 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(11, 11, 100, 101, 102, 103, 104,
                                                105, {{9, 106}}, {{14, 107}});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -785,13 +779,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 8, 100, 101, 102, 103, 104,
                                                105, {}, {});
   ASSERT_FALSE(
@@ -835,13 +827,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 11, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
@@ -886,13 +876,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 11, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
@@ -938,13 +926,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 11, /* not relevant */ 200,
                                                100, 101, 102, 103,
                                                /* not relevant */ 201, {}, {});
@@ -982,13 +968,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 8, 100, 101, 102, 103, 104,
                                                105, {}, {});
   ASSERT_FALSE(
@@ -1026,13 +1010,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(7, 8, 100, 101, 102, 103, 104,
                                                105, {}, {});
   ASSERT_FALSE(
@@ -1069,13 +1051,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 7, 100, 101, 102, 103, 104,
                                                105, {}, {});
   ASSERT_FALSE(
@@ -1114,13 +1094,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(6, 7, 100, 101, 102, 103, 104,
                                                105, {}, {});
   ASSERT_FALSE(
@@ -1159,13 +1137,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(8, 11, 100, 101, 102, 103, 104,
                                                105, {}, {});
   ASSERT_FALSE(
@@ -1201,13 +1177,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 54,
       /*exit_block*/ 58,
@@ -1222,8 +1196,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1292,13 +1267,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 9,
       /*exit_block*/ 10,
@@ -1313,8 +1286,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1387,13 +1361,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 54,
       /*exit_block*/ 54,
@@ -1408,8 +1380,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1477,13 +1450,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 54,
       /*exit_block*/ 54,
@@ -1498,8 +1469,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -1563,13 +1535,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 21,
       /*exit_block*/ 21,
@@ -1620,13 +1590,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 21,
       /*exit_block*/ 24,
@@ -1677,13 +1645,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 5,
       /*exit_block*/ 22,
@@ -1737,13 +1703,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 13,
       /*exit_block*/ 15,
@@ -1799,13 +1763,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 13,
       /*exit_block*/ 15,
@@ -1866,13 +1828,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 11,
       /*exit_block*/ 11,
@@ -1887,8 +1847,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -2022,13 +1983,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactFunctionIsLivesafe(30);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
       200);
@@ -2049,8 +2008,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // The original function should still be livesafe.
   ASSERT_TRUE(transformation_context.GetFactManager()->FunctionIsLivesafe(30));
@@ -2251,13 +2211,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   for (uint32_t block_id : {16u, 23u, 24u, 26u, 27u, 34u, 35u, 50u}) {
     transformation_context.GetFactManager()->AddFactBlockIsDead(block_id);
   }
@@ -2276,8 +2234,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   // All the original blocks, plus the new function entry block, should be dead.
   for (uint32_t block_id : {16u, 23u, 24u, 26u, 27u, 34u, 35u, 50u, 203u}) {
     ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(block_id));
@@ -2335,13 +2294,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   for (uint32_t block_id : {32u, 34u, 35u}) {
     transformation_context.GetFactManager()->AddFactBlockIsDead(block_id);
   }
@@ -2360,8 +2317,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   // The blocks that were originally dead, but not others, should be dead.
   for (uint32_t block_id : {32u, 34u, 35u}) {
     ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(block_id));
@@ -2420,13 +2378,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(9);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
       14);
@@ -2445,8 +2401,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   // The variables that were originally irrelevant, plus input parameters
   // corresponding to them, should be irrelevant.  The rest should not be.
   for (uint32_t variable_id : {9u, 14u, 206u, 208u}) {
@@ -2496,13 +2453,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 80,
       /*exit_block*/ 80,
@@ -2554,13 +2509,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 22,
       /*exit_block*/ 23,
@@ -2575,13 +2528,106 @@
 
   ASSERT_FALSE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+}
+
+TEST(TransformationOutlineFunctionTest, SkipVoidOutputId) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %21 = OpTypeBool
+         %81 = OpConstantTrue %21
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpBranch %80
+         %80 = OpLabel
+         %84 = OpFunctionCall %2 %87
+               OpBranch %90
+         %90 = OpLabel
+         %86 = OpPhi %2 %84 %80
+               OpReturn
+               OpFunctionEnd
+         %87 = OpFunction %2 None %3
+         %88 = 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);
+  TransformationOutlineFunction transformation(
+      /*entry_block*/ 80,
+      /*exit_block*/ 80,
+      /*new_function_struct_return_type_id*/ 300,
+      /*new_function_type_id*/ 301,
+      /*new_function_id*/ 302,
+      /*new_function_region_entry_block*/ 304,
+      /*new_caller_result_id*/ 305,
+      /*new_callee_result_id*/ 306,
+      /*input_id_to_fresh_id*/ {},
+      /*output_id_to_fresh_id*/ {{84, 307}});
+
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %6 "main"
+               OpExecutionMode %6 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+         %21 = OpTypeBool
+         %81 = OpConstantTrue %21
+        %300 = OpTypeStruct
+        %301 = OpTypeFunction %300
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpBranch %80
+         %80 = OpLabel
+        %305 = OpFunctionCall %300 %302
+         %84 = OpUndef %2
+               OpBranch %90
+         %90 = OpLabel
+         %86 = OpPhi %2 %84 %80
+               OpReturn
+               OpFunctionEnd
+         %87 = OpFunction %2 None %3
+         %88 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        %302 = OpFunction %300 None %301
+        %304 = OpLabel
+        %307 = OpFunctionCall %2 %87
+        %306 = OpCompositeConstruct %300
+               OpReturnValue %306
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
 TEST(TransformationOutlineFunctionTest, Miscellaneous1) {
-  // This tests outlining of some non-trivial code.
+  // This tests outlining of some non-trivial code, and also tests the way
+  // overflow ids are used by the transformation.
 
-  std::string shader = R"(
+  std::string reference_shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
@@ -2682,13 +2728,7 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
-  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 150,
@@ -2702,123 +2742,323 @@
       /*input_id_to_fresh_id*/ {{102, 300}, {103, 301}, {40, 302}},
       /*output_id_to_fresh_id*/ {{106, 400}, {107, 401}});
 
-  ASSERT_TRUE(
-      transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationOutlineFunction transformation_with_missing_input_id(
+      /*entry_block*/ 150,
+      /*exit_block*/ 1001,
+      /*new_function_struct_return_type_id*/ 200,
+      /*new_function_type_id*/ 201,
+      /*new_function_id*/ 202,
+      /*new_function_region_entry_block*/ 203,
+      /*new_caller_result_id*/ 204,
+      /*new_callee_result_id*/ 205,
+      /*input_id_to_fresh_id*/ {{102, 300}, {40, 302}},
+      /*output_id_to_fresh_id*/ {{106, 400}, {107, 401}});
 
-  std::string after_transformation = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %85
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %28 "buf"
-               OpMemberName %28 0 "u1"
-               OpMemberName %28 1 "u2"
-               OpName %30 ""
-               OpName %85 "color"
-               OpMemberDecorate %28 0 Offset 0
-               OpMemberDecorate %28 1 Offset 4
-               OpDecorate %28 Block
-               OpDecorate %30 DescriptorSet 0
-               OpDecorate %30 Binding 0
-               OpDecorate %85 Location 0
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypeVector %6 4
-         %10 = OpConstant %6 1
-         %11 = OpConstant %6 2
-         %12 = OpConstant %6 3
-         %13 = OpConstant %6 4
-         %14 = OpConstantComposite %7 %10 %11 %12 %13
-         %15 = OpTypeInt 32 1
-         %18 = OpConstant %15 0
-         %28 = OpTypeStruct %6 %6
-         %29 = OpTypePointer Uniform %28
-         %30 = OpVariable %29 Uniform
-         %31 = OpTypePointer Uniform %6
-         %35 = OpTypeBool
-         %39 = OpConstant %15 1
-         %84 = OpTypePointer Output %7
-         %85 = OpVariable %84 Output
-        %114 = OpConstant %15 8
-        %200 = OpTypeStruct %7 %15
-        %201 = OpTypeFunction %200 %15 %7 %15
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpBranch %22
-         %22 = OpLabel
-        %103 = OpPhi %15 %18 %5 %106 %43
-        %102 = OpPhi %7 %14 %5 %107 %43
-        %101 = OpPhi %15 %18 %5 %40 %43
-         %32 = OpAccessChain %31 %30 %18
-         %33 = OpLoad %6 %32
-         %34 = OpConvertFToS %15 %33
-         %36 = OpSLessThan %35 %101 %34
-               OpLoopMerge %24 %43 None
-               OpBranchConditional %36 %23 %24
-         %23 = OpLabel
-         %40 = OpIAdd %15 %101 %39
-               OpBranch %150
-        %150 = OpLabel
-        %204 = OpFunctionCall %200 %202 %103 %102 %40
-        %107 = OpCompositeExtract %7 %204 0
-        %106 = OpCompositeExtract %15 %204 1
-               OpBranch %43
-         %43 = OpLabel
-               OpBranch %22
-         %24 = OpLabel
-         %87 = OpCompositeExtract %6 %102 0
-         %91 = OpConvertSToF %6 %103
-         %92 = OpCompositeConstruct %7 %87 %11 %91 %10
-               OpStore %85 %92
-               OpReturn
-               OpFunctionEnd
-        %202 = OpFunction %200 None %201
-        %301 = OpFunctionParameter %15
-        %300 = OpFunctionParameter %7
-        %302 = OpFunctionParameter %15
-        %203 = OpLabel
-               OpBranch %41
-         %41 = OpLabel
-        %401 = OpPhi %7 %300 %203 %111 %65
-        %400 = OpPhi %15 %301 %203 %110 %65
-        %104 = OpPhi %15 %302 %203 %81 %65
-         %47 = OpAccessChain %31 %30 %39
-         %48 = OpLoad %6 %47
-         %49 = OpConvertFToS %15 %48
-         %50 = OpSLessThan %35 %104 %49
-               OpLoopMerge %1000 %65 None
-               OpBranchConditional %50 %42 %1000
-         %42 = OpLabel
-         %60 = OpIAdd %15 %400 %114
-         %63 = OpSGreaterThan %35 %104 %60
-               OpBranchConditional %63 %64 %65
-         %64 = OpLabel
-         %71 = OpCompositeExtract %6 %401 0
-         %72 = OpFAdd %6 %71 %11
-         %97 = OpCompositeInsert %7 %72 %401 0
-         %76 = OpCompositeExtract %6 %401 3
-         %77 = OpConvertFToS %15 %76
-         %79 = OpIAdd %15 %60 %77
-               OpBranch %65
-         %65 = OpLabel
-        %111 = OpPhi %7 %401 %42 %97 %64
-        %110 = OpPhi %15 %60 %42 %79 %64
-         %81 = OpIAdd %15 %104 %39
-               OpBranch %41
-       %1000 = OpLabel
-               OpBranch %1001
-       %1001 = OpLabel
-        %205 = OpCompositeConstruct %200 %401 %400
-               OpReturnValue %205
-               OpFunctionEnd
-  )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+  TransformationOutlineFunction transformation_with_missing_output_id(
+      /*entry_block*/ 150,
+      /*exit_block*/ 1001,
+      /*new_function_struct_return_type_id*/ 200,
+      /*new_function_type_id*/ 201,
+      /*new_function_id*/ 202,
+      /*new_function_region_entry_block*/ 203,
+      /*new_caller_result_id*/ 204,
+      /*new_callee_result_id*/ 205,
+      /*input_id_to_fresh_id*/ {{102, 300}, {103, 301}, {40, 302}},
+      /*output_id_to_fresh_id*/ {{106, 400}});
+
+  TransformationOutlineFunction
+      transformation_with_missing_input_and_output_ids(
+          /*entry_block*/ 150,
+          /*exit_block*/ 1001,
+          /*new_function_struct_return_type_id*/ 200,
+          /*new_function_type_id*/ 201,
+          /*new_function_id*/ 202,
+          /*new_function_region_entry_block*/ 203,
+          /*new_caller_result_id*/ 204,
+          /*new_callee_result_id*/ 205,
+          /*input_id_to_fresh_id*/ {{102, 300}, {40, 302}},
+          /*output_id_to_fresh_id*/ {{106, 400}});
+
+  {
+    const auto context =
+        BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+
+    TransformationContext transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options);
+
+#ifndef NDEBUG
+    // We expect the following applicability checks to lead to assertion
+    // failures since the transformations are missing input or output ids, and
+    // the transformation context does not have a source of overflow ids.
+    ASSERT_DEATH(transformation_with_missing_input_id.IsApplicable(
+                     context.get(), transformation_context),
+                 "Bad attempt to query whether overflow ids are available.");
+    ASSERT_DEATH(transformation_with_missing_output_id.IsApplicable(
+                     context.get(), transformation_context),
+                 "Bad attempt to query whether overflow ids are available.");
+    ASSERT_DEATH(transformation_with_missing_input_and_output_ids.IsApplicable(
+                     context.get(), transformation_context),
+                 "Bad attempt to query whether overflow ids are available.");
+#endif
+
+    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 variant_shader = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %4 "main" %85
+                 OpExecutionMode %4 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %4 "main"
+                 OpName %28 "buf"
+                 OpMemberName %28 0 "u1"
+                 OpMemberName %28 1 "u2"
+                 OpName %30 ""
+                 OpName %85 "color"
+                 OpMemberDecorate %28 0 Offset 0
+                 OpMemberDecorate %28 1 Offset 4
+                 OpDecorate %28 Block
+                 OpDecorate %30 DescriptorSet 0
+                 OpDecorate %30 Binding 0
+                 OpDecorate %85 Location 0
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeVector %6 4
+           %10 = OpConstant %6 1
+           %11 = OpConstant %6 2
+           %12 = OpConstant %6 3
+           %13 = OpConstant %6 4
+           %14 = OpConstantComposite %7 %10 %11 %12 %13
+           %15 = OpTypeInt 32 1
+           %18 = OpConstant %15 0
+           %28 = OpTypeStruct %6 %6
+           %29 = OpTypePointer Uniform %28
+           %30 = OpVariable %29 Uniform
+           %31 = OpTypePointer Uniform %6
+           %35 = OpTypeBool
+           %39 = OpConstant %15 1
+           %84 = OpTypePointer Output %7
+           %85 = OpVariable %84 Output
+          %114 = OpConstant %15 8
+          %200 = OpTypeStruct %7 %15
+          %201 = OpTypeFunction %200 %15 %7 %15
+            %4 = OpFunction %2 None %3
+            %5 = OpLabel
+                 OpBranch %22
+           %22 = OpLabel
+          %103 = OpPhi %15 %18 %5 %106 %43
+          %102 = OpPhi %7 %14 %5 %107 %43
+          %101 = OpPhi %15 %18 %5 %40 %43
+           %32 = OpAccessChain %31 %30 %18
+           %33 = OpLoad %6 %32
+           %34 = OpConvertFToS %15 %33
+           %36 = OpSLessThan %35 %101 %34
+                 OpLoopMerge %24 %43 None
+                 OpBranchConditional %36 %23 %24
+           %23 = OpLabel
+           %40 = OpIAdd %15 %101 %39
+                 OpBranch %150
+          %150 = OpLabel
+          %204 = OpFunctionCall %200 %202 %103 %102 %40
+          %107 = OpCompositeExtract %7 %204 0
+          %106 = OpCompositeExtract %15 %204 1
+                 OpBranch %43
+           %43 = OpLabel
+                 OpBranch %22
+           %24 = OpLabel
+           %87 = OpCompositeExtract %6 %102 0
+           %91 = OpConvertSToF %6 %103
+           %92 = OpCompositeConstruct %7 %87 %11 %91 %10
+                 OpStore %85 %92
+                 OpReturn
+                 OpFunctionEnd
+          %202 = OpFunction %200 None %201
+          %301 = OpFunctionParameter %15
+          %300 = OpFunctionParameter %7
+          %302 = OpFunctionParameter %15
+          %203 = OpLabel
+                 OpBranch %41
+           %41 = OpLabel
+          %401 = OpPhi %7 %300 %203 %111 %65
+          %400 = OpPhi %15 %301 %203 %110 %65
+          %104 = OpPhi %15 %302 %203 %81 %65
+           %47 = OpAccessChain %31 %30 %39
+           %48 = OpLoad %6 %47
+           %49 = OpConvertFToS %15 %48
+           %50 = OpSLessThan %35 %104 %49
+                 OpLoopMerge %1000 %65 None
+                 OpBranchConditional %50 %42 %1000
+           %42 = OpLabel
+           %60 = OpIAdd %15 %400 %114
+           %63 = OpSGreaterThan %35 %104 %60
+                 OpBranchConditional %63 %64 %65
+           %64 = OpLabel
+           %71 = OpCompositeExtract %6 %401 0
+           %72 = OpFAdd %6 %71 %11
+           %97 = OpCompositeInsert %7 %72 %401 0
+           %76 = OpCompositeExtract %6 %401 3
+           %77 = OpConvertFToS %15 %76
+           %79 = OpIAdd %15 %60 %77
+                 OpBranch %65
+           %65 = OpLabel
+          %111 = OpPhi %7 %401 %42 %97 %64
+          %110 = OpPhi %15 %60 %42 %79 %64
+           %81 = OpIAdd %15 %104 %39
+                 OpBranch %41
+         %1000 = OpLabel
+                 OpBranch %1001
+         %1001 = OpLabel
+          %205 = OpCompositeConstruct %200 %401 %400
+                 OpReturnValue %205
+                 OpFunctionEnd
+    )";
+    ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+  }
+
+  {
+    const auto context =
+        BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    auto overflow_ids_unique_ptr = MakeUnique<CounterOverflowIdSource>(2000);
+    auto overflow_ids_ptr = overflow_ids_unique_ptr.get();
+    TransformationContext new_transformation_context(
+        MakeUnique<FactManager>(context.get()), validator_options,
+        std::move(overflow_ids_unique_ptr));
+    ASSERT_TRUE(transformation_with_missing_input_id.IsApplicable(
+        context.get(), new_transformation_context));
+    ASSERT_TRUE(transformation_with_missing_output_id.IsApplicable(
+        context.get(), new_transformation_context));
+    ASSERT_TRUE(transformation_with_missing_input_and_output_ids.IsApplicable(
+        context.get(), new_transformation_context));
+    ApplyAndCheckFreshIds(transformation_with_missing_input_and_output_ids,
+                          context.get(), &new_transformation_context,
+                          overflow_ids_ptr->GetIssuedOverflowIds());
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+
+    std::string variant_shader = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %4 "main" %85
+                 OpExecutionMode %4 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %4 "main"
+                 OpName %28 "buf"
+                 OpMemberName %28 0 "u1"
+                 OpMemberName %28 1 "u2"
+                 OpName %30 ""
+                 OpName %85 "color"
+                 OpMemberDecorate %28 0 Offset 0
+                 OpMemberDecorate %28 1 Offset 4
+                 OpDecorate %28 Block
+                 OpDecorate %30 DescriptorSet 0
+                 OpDecorate %30 Binding 0
+                 OpDecorate %85 Location 0
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeVector %6 4
+           %10 = OpConstant %6 1
+           %11 = OpConstant %6 2
+           %12 = OpConstant %6 3
+           %13 = OpConstant %6 4
+           %14 = OpConstantComposite %7 %10 %11 %12 %13
+           %15 = OpTypeInt 32 1
+           %18 = OpConstant %15 0
+           %28 = OpTypeStruct %6 %6
+           %29 = OpTypePointer Uniform %28
+           %30 = OpVariable %29 Uniform
+           %31 = OpTypePointer Uniform %6
+           %35 = OpTypeBool
+           %39 = OpConstant %15 1
+           %84 = OpTypePointer Output %7
+           %85 = OpVariable %84 Output
+          %114 = OpConstant %15 8
+          %200 = OpTypeStruct %7 %15
+          %201 = OpTypeFunction %200 %15 %7 %15
+            %4 = OpFunction %2 None %3
+            %5 = OpLabel
+                 OpBranch %22
+           %22 = OpLabel
+          %103 = OpPhi %15 %18 %5 %106 %43
+          %102 = OpPhi %7 %14 %5 %107 %43
+          %101 = OpPhi %15 %18 %5 %40 %43
+           %32 = OpAccessChain %31 %30 %18
+           %33 = OpLoad %6 %32
+           %34 = OpConvertFToS %15 %33
+           %36 = OpSLessThan %35 %101 %34
+                 OpLoopMerge %24 %43 None
+                 OpBranchConditional %36 %23 %24
+           %23 = OpLabel
+           %40 = OpIAdd %15 %101 %39
+                 OpBranch %150
+          %150 = OpLabel
+          %204 = OpFunctionCall %200 %202 %103 %102 %40
+          %107 = OpCompositeExtract %7 %204 0
+          %106 = OpCompositeExtract %15 %204 1
+                 OpBranch %43
+           %43 = OpLabel
+                 OpBranch %22
+           %24 = OpLabel
+           %87 = OpCompositeExtract %6 %102 0
+           %91 = OpConvertSToF %6 %103
+           %92 = OpCompositeConstruct %7 %87 %11 %91 %10
+                 OpStore %85 %92
+                 OpReturn
+                 OpFunctionEnd
+          %202 = OpFunction %200 None %201
+         %2000 = OpFunctionParameter %15
+          %300 = OpFunctionParameter %7
+          %302 = OpFunctionParameter %15
+          %203 = OpLabel
+                 OpBranch %41
+           %41 = OpLabel
+         %2001 = OpPhi %7 %300 %203 %111 %65
+          %400 = OpPhi %15 %2000 %203 %110 %65
+          %104 = OpPhi %15 %302 %203 %81 %65
+           %47 = OpAccessChain %31 %30 %39
+           %48 = OpLoad %6 %47
+           %49 = OpConvertFToS %15 %48
+           %50 = OpSLessThan %35 %104 %49
+                 OpLoopMerge %1000 %65 None
+                 OpBranchConditional %50 %42 %1000
+           %42 = OpLabel
+           %60 = OpIAdd %15 %400 %114
+           %63 = OpSGreaterThan %35 %104 %60
+                 OpBranchConditional %63 %64 %65
+           %64 = OpLabel
+           %71 = OpCompositeExtract %6 %2001 0
+           %72 = OpFAdd %6 %71 %11
+           %97 = OpCompositeInsert %7 %72 %2001 0
+           %76 = OpCompositeExtract %6 %2001 3
+           %77 = OpConvertFToS %15 %76
+           %79 = OpIAdd %15 %60 %77
+                 OpBranch %65
+           %65 = OpLabel
+          %111 = OpPhi %7 %2001 %42 %97 %64
+          %110 = OpPhi %15 %60 %42 %79 %64
+           %81 = OpIAdd %15 %104 %39
+                 OpBranch %41
+         %1000 = OpLabel
+                 OpBranch %1001
+         %1001 = OpLabel
+          %205 = OpCompositeConstruct %200 %2001 %400
+                 OpReturnValue %205
+                 OpFunctionEnd
+    )";
+    ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+  }
 }
 
 TEST(TransformationOutlineFunctionTest, Miscellaneous2) {
@@ -2852,13 +3092,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 38,
       /*exit_block*/ 36,
@@ -2911,13 +3149,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 80,
       /*exit_block*/ 81,
@@ -2932,8 +3168,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -3004,13 +3241,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationOutlineFunction transformation(
       /*entry_block*/ 80,
       /*exit_block*/ 106,
@@ -3025,8 +3260,9 @@
 
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_permute_function_parameters_test.cpp b/test/fuzz/transformation_permute_function_parameters_test.cpp
index 4046f79..c1eb125 100644
--- a/test/fuzz/transformation_permute_function_parameters_test.cpp
+++ b/test/fuzz/transformation_permute_function_parameters_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_permute_function_parameters.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -251,13 +253,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Can't permute main function
   ASSERT_FALSE(TransformationPermuteFunctionParameters(4, 105, {})
                    .IsApplicable(context.get(), transformation_context));
@@ -294,36 +294,46 @@
     TransformationPermuteFunctionParameters transformation(12, 105, {1, 0});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     TransformationPermuteFunctionParameters transformation(28, 106, {1, 0});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     TransformationPermuteFunctionParameters transformation(200, 107, {1, 0});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     TransformationPermuteFunctionParameters transformation(219, 108, {1, 0});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
   {
     TransformationPermuteFunctionParameters transformation(229, 109, {1, 0});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_permute_phi_operands_test.cpp b/test/fuzz/transformation_permute_phi_operands_test.cpp
index c0a428a..2843cfc 100644
--- a/test/fuzz/transformation_permute_phi_operands_test.cpp
+++ b/test/fuzz/transformation_permute_phi_operands_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_permute_phi_operands.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -65,13 +68,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Result id is invalid.
   ASSERT_FALSE(TransformationPermutePhiOperands(26, {}).IsApplicable(
       context.get(), transformation_context));
@@ -102,7 +103,7 @@
   TransformationPermutePhiOperands transformation(25, {1, 0});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_propagate_instruction_down_test.cpp b/test/fuzz/transformation_propagate_instruction_down_test.cpp
new file mode 100644
index 0000000..52974ca
--- /dev/null
+++ b/test/fuzz/transformation_propagate_instruction_down_test.cpp
@@ -0,0 +1,1138 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_propagate_instruction_down.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/counter_overflow_id_source.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPropagateInstructionDownTest, 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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %9 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          ; Has no instruction to propagate
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+          %8 = OpCopyObject %6 %7
+               OpStore %10 %8
+               OpBranch %11
+
+        ; Unreachable block
+        %100 = OpLabel
+        %101 = OpCopyObject %6 %7
+               OpBranch %11
+
+         ; Selection header
+         ;
+         ; One of acceptable successors has an OpPhi that uses propagated
+         ; instruction's id
+         %11 = OpLabel
+         %19 = OpCopyObject %6 %7
+               OpSelectionMerge %18 None
+               OpBranchConditional %13 %14 %18
+
+         ; %16 has no acceptable successors
+         %14 = OpLabel
+         %20 = OpPhi %6 %19 %11
+         %15 = OpCopyObject %6 %7 ; dependency
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpCopyObject %6 %15
+               OpBranch %18
+
+         ; Can be applied
+         %18 = OpLabel
+         %21 = OpCopyObject %6 %7
+               OpSelectionMerge %24 None
+               OpBranchConditional %13 %22 %23
+         %22 = OpLabel
+         %29 = OpPhi %6 %7 %18
+               OpStore %10 %21
+               OpBranch %24
+         %23 = OpLabel
+               OpStore %10 %21
+               OpBranch %24
+         %24 = OpLabel
+               OpStore %10 %21
+               OpBranch %32
+
+         ; Can't replace all uses of the propagated instruction: %30 is
+         ; propagated into %27.
+         %32 = OpLabel
+               OpLoopMerge %28 %27 None
+               OpBranchConditional %13 %26 %28
+         %26 = OpLabel
+         %25 = OpCopyObject %6 %7
+         %30 = OpCopyObject %6 %25
+               OpBranchConditional %13 %27 %28
+         %27 = OpLabel
+               OpBranch %32
+         %28 = OpLabel
+         %31 = OpPhi %6 %30 %26 %7 %32 ; Can't replace this use
+               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);
+
+  // Invalid block id.
+  ASSERT_FALSE(TransformationPropagateInstructionDown(200, 200, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationPropagateInstructionDown(101, 200, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The block is unreachable.
+  ASSERT_FALSE(TransformationPropagateInstructionDown(100, 200, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The block has no instruction to propagate.
+  ASSERT_FALSE(TransformationPropagateInstructionDown(5, 200, {{{11, 201}}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The block has no acceptable successors.
+  ASSERT_FALSE(TransformationPropagateInstructionDown(16, 200, {{{18, 201}}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // One of acceptable successors has an OpPhi that uses propagated
+  // instruction's id.
+  ASSERT_FALSE(
+      TransformationPropagateInstructionDown(11, 200, {{{14, 201}, {18, 202}}})
+          .IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  // Not all fresh ids are provided.
+  ASSERT_DEATH(
+      TransformationPropagateInstructionDown(18, 200, {{{22, 201}, {202, 203}}})
+          .IsApplicable(context.get(), transformation_context),
+      "Bad attempt to query whether overflow ids are available.");
+#endif
+
+  // Not all fresh ids are fresh.
+  ASSERT_FALSE(TransformationPropagateInstructionDown(
+                   18, 18, {{{22, 201}, {23, 202}, {202, 203}}})
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationPropagateInstructionDown(
+                   18, 200, {{{22, 22}, {23, 202}, {202, 203}}})
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationPropagateInstructionDown(
+                   18, 18, {{{22, 22}, {23, 202}, {202, 203}}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Not all fresh ids are unique.
+  ASSERT_FALSE(TransformationPropagateInstructionDown(
+                   18, 200, {{{22, 200}, {23, 202}, {202, 200}}})
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationPropagateInstructionDown(
+                   18, 200, {{{22, 201}, {23, 202}, {202, 200}}})
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationPropagateInstructionDown(
+                   18, 200, {{{22, 201}, {23, 201}, {202, 203}}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Can't replace all uses of the propagated instruction: %30 is propagated
+  // into %27.
+  ASSERT_FALSE(TransformationPropagateInstructionDown(26, 200, {{{27, 201}}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  {
+    TransformationPropagateInstructionDown transformation(
+        18, 200, {{{22, 201}, {23, 202}, {202, 203}}});
+    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()->IsSynonymous(
+        MakeDataDescriptor(201, {}), MakeDataDescriptor(202, {})));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(201, {}), MakeDataDescriptor(200, {})));
+  }
+
+  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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %9 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          ; Has no instruction to propagate
+          %5 = OpLabel
+         %10 = OpVariable %9 Function
+          %8 = OpCopyObject %6 %7
+               OpStore %10 %8
+               OpBranch %11
+
+        ; Unreachable block
+        %100 = OpLabel
+        %101 = OpCopyObject %6 %7
+               OpBranch %11
+
+         ; Selection header
+         ;
+         ; One of acceptable successors has an OpPhi that uses propagated
+         ; instruction's id
+         %11 = OpLabel
+         %19 = OpCopyObject %6 %7
+               OpSelectionMerge %18 None
+               OpBranchConditional %13 %14 %18
+
+         ; %16 has no acceptable successors
+         %14 = OpLabel
+         %20 = OpPhi %6 %19 %11
+         %15 = OpCopyObject %6 %7 ; dependency
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpCopyObject %6 %15
+               OpBranch %18
+
+         ; Can be applied
+         %18 = OpLabel
+               OpSelectionMerge %24 None
+               OpBranchConditional %13 %22 %23
+         %22 = OpLabel
+         %29 = OpPhi %6 %7 %18
+        %201 = OpCopyObject %6 %7
+               OpStore %10 %201
+               OpBranch %24
+         %23 = OpLabel
+        %202 = OpCopyObject %6 %7
+               OpStore %10 %202
+               OpBranch %24
+         %24 = OpLabel
+        %200 = OpPhi %6 %201 %22 %202 %23
+               OpStore %10 %200
+               OpBranch %32
+
+         ; Can't replace all uses of the propagated instruction: %30 is
+         ; propagated into %27.
+         %32 = OpLabel
+               OpLoopMerge %28 %27 None
+               OpBranchConditional %13 %26 %28
+         %26 = OpLabel
+         %25 = OpCopyObject %6 %7
+         %30 = OpCopyObject %6 %25
+               OpBranchConditional %13 %27 %28
+         %27 = OpLabel
+               OpBranch %32
+         %28 = OpLabel
+         %31 = OpPhi %6 %30 %26 %7 %32 ; Can't replace this use
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionDownTest, CantCreateOpPhiTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+
+          ; %5 doesn't belong to any construct
+          %5 = OpLabel
+         %15 = OpCopyObject %6 %7
+               OpBranch %16
+
+         ; The merge block (%19) is unreachable
+         %16 = OpLabel
+         %17 = OpCopyObject %6 %7
+               OpSelectionMerge %19 None
+               OpBranchConditional %13 %18 %18
+
+         ; %21 doesn't dominate the merge block - %20
+         %18 = OpLabel
+               OpSelectionMerge %20 None
+               OpBranchConditional %13 %20 %21
+         %21 = OpLabel
+         %22 = OpCopyObject %6 %7
+               OpBranch %20
+
+         ; The merge block (%24) is an acceptable successor of the propagated
+         ; instruction's block
+         %20 = OpLabel
+         %23 = OpCopyObject %6 %7
+               OpSelectionMerge %24 None
+               OpBranchConditional %13 %24 %30
+         %30 = OpLabel
+               OpBranch %24
+
+         ; One of the predecessors of the merge block is not dominated by any
+         ; successor of the propagated instruction's block
+         %24 = OpLabel
+         %26 = OpCopyObject %6 %7
+               OpLoopMerge %29 %25 None
+               OpBranch %25
+         %25 = OpLabel
+               OpBranchConditional %13 %24 %29
+         %28 = OpLabel ; unreachable predecessor of %29
+               OpBranch %29
+         %29 = OpLabel
+               OpReturn
+
+         %19 = 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);
+
+  TransformationPropagateInstructionDown transformations[] = {
+      // %5 doesn't belong to any construct.
+      {5, 200, {{{16, 201}}}},
+
+      // The merge block (%19) is unreachable.
+      {16, 200, {{{18, 202}}}},
+
+      // %21 doesn't dominate the merge block - %20.
+      {21, 200, {{{20, 203}}}},
+
+      // The merge block (%24) is an acceptable successor of the propagated
+      // instruction's block.
+      {20, 200, {{{24, 204}, {30, 205}}}},
+
+      // One of the predecessors of the merge block is not dominated by any
+      // successor of the propagated instruction's block.
+      {24, 200, {{{25, 206}}}},
+  };
+
+  for (const auto& transformation : transformations) {
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  // No transformation has introduced an OpPhi instruction.
+  ASSERT_FALSE(context->get_def_use_mgr()->GetDef(200));
+
+  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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+
+          ; %5 doesn't belong to any construct
+          %5 = OpLabel
+               OpBranch %16
+
+         ; The merge block (%19) is unreachable
+         %16 = OpLabel
+        %201 = OpCopyObject %6 %7
+               OpSelectionMerge %19 None
+               OpBranchConditional %13 %18 %18
+
+         ; %21 doesn't dominate the merge block - %20
+         %18 = OpLabel
+        %202 = OpCopyObject %6 %7
+               OpSelectionMerge %20 None
+               OpBranchConditional %13 %20 %21
+         %21 = OpLabel
+               OpBranch %20
+
+         ; The merge block (%24) is an acceptable successor of the propagated
+         ; instruction's block
+         %20 = OpLabel
+        %203 = OpCopyObject %6 %7
+               OpSelectionMerge %24 None
+               OpBranchConditional %13 %24 %30
+         %30 = OpLabel
+        %205 = OpCopyObject %6 %7
+               OpBranch %24
+
+         ; One of the predecessors of the merge block is not dominated by any
+         ; successor of the propagated instruction's block
+         %24 = OpLabel
+        %204 = OpCopyObject %6 %7
+               OpLoopMerge %29 %25 None
+               OpBranch %25
+         %25 = OpLabel
+        %206 = OpCopyObject %6 %7
+               OpBranchConditional %13 %24 %29
+         %28 = OpLabel ; unreachable predecessor of %29
+               OpBranch %29
+         %29 = OpLabel
+               OpReturn
+
+         %19 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionDownTest, VariablePointersCapability) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %10 = OpTypePointer Workgroup %6
+         %11 = OpVariable %10 Workgroup
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %18 = OpCopyObject %10 %11
+         %14 = OpCopyObject %10 %11
+               OpSelectionMerge %17 None
+               OpBranchConditional %13 %15 %16
+         %15 = OpLabel
+               OpBranch %17
+         %16 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpStore %18 %7
+               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);
+
+  {
+    // Can propagate a pointer only if we don't have to create an OpPhi.
+    TransformationPropagateInstructionDown transformation(
+        5, 200, {{{15, 201}, {16, 202}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+    ASSERT_FALSE(context->get_def_use_mgr()->GetDef(200));
+  }
+  {
+    // Can't propagate a pointer if there is no VariablePointersStorageBuffer
+    // capability and we need to create an OpPhi.
+    TransformationPropagateInstructionDown transformation(
+        5, 200, {{{15, 203}, {16, 204}}});
+    ASSERT_FALSE(context->get_feature_mgr()->HasCapability(
+        SpvCapabilityVariablePointersStorageBuffer));
+    ASSERT_FALSE(
+        transformation.IsApplicable(context.get(), transformation_context));
+
+    context->AddCapability(SpvCapabilityVariablePointers);
+    ASSERT_TRUE(context->get_feature_mgr()->HasCapability(
+        SpvCapabilityVariablePointersStorageBuffer));
+    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 VariablePointers
+          %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
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %10 = OpTypePointer Workgroup %6
+         %11 = OpVariable %10 Workgroup
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %17 None
+               OpBranchConditional %13 %15 %16
+         %15 = OpLabel
+        %203 = OpCopyObject %10 %11
+        %201 = OpCopyObject %10 %11
+               OpBranch %17
+         %16 = OpLabel
+        %204 = OpCopyObject %10 %11
+        %202 = OpCopyObject %10 %11
+               OpBranch %17
+         %17 = OpLabel
+        %200 = OpPhi %10 %203 %15 %204 %16
+               OpStore %200 %7
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionDownTest, UseOverflowIdsTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %10 = OpTypePointer Private %6
+         %11 = OpVariable %10 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpCopyObject %6 %7
+               OpSelectionMerge %23 None
+               OpBranchConditional %13 %21 %22
+         %21 = OpLabel
+               OpStore %11 %20
+               OpBranch %23
+         %22 = OpLabel
+               OpStore %11 %20
+               OpBranch %23
+         %23 = OpLabel
+               OpStore %11 %20
+               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,
+      MakeUnique<CounterOverflowIdSource>(300));
+
+  TransformationPropagateInstructionDown transformation(5, 200, {{{21, 201}}});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context,
+                        {300});
+  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 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %10 = OpTypePointer Private %6
+         %11 = OpVariable %10 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %23 None
+               OpBranchConditional %13 %21 %22
+         %21 = OpLabel
+        %201 = OpCopyObject %6 %7
+               OpStore %11 %201
+               OpBranch %23
+         %22 = OpLabel
+        %300 = OpCopyObject %6 %7
+               OpStore %11 %300
+               OpBranch %23
+         %23 = OpLabel
+        %200 = OpPhi %6 %201 %21 %300 %22
+               OpStore %11 %200
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionDownTest, TestCreatedFacts) {
+  std::string shader = R"(
+               OpCapability VariablePointers
+          %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
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %10 = OpTypePointer Private %6
+         %11 = OpVariable %10 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpCopyObject %6 %7
+         %24 = OpCopyObject %6 %7 ; Irrelevant id
+         %25 = OpCopyObject %10 %11 ; Pointee is irrelevant
+               OpSelectionMerge %23 None
+               OpBranchConditional %13 %21 %22
+         %21 = OpLabel
+               OpStore %25 %20
+               OpBranch %23
+         %22 = OpLabel ; Dead block
+               OpStore %25 %20
+               OpBranch %23
+         %23 = OpLabel
+               OpStore %25 %20
+               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()->AddFactBlockIsDead(22);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(24);
+  transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+      25);
+
+  {
+    // Propagate pointer with PointeeIsIrrelevant fact.
+    TransformationPropagateInstructionDown transformation(
+        5, 200, {{{21, 201}, {22, 202}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(201));
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(202));
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(200));
+
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(201));
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(202));
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(200));
+
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(201, {}), MakeDataDescriptor(202, {})));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(201, {}), MakeDataDescriptor(200, {})));
+  }
+  {
+    // Propagate an irrelevant id.
+    TransformationPropagateInstructionDown transformation(
+        5, 203, {{{21, 204}, {22, 205}}});
+    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()->IdIsIrrelevant(203));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(204));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(205));
+
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(203));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(204));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(205));
+
+    ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(204, {}), MakeDataDescriptor(205, {})));
+    ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(204, {}), MakeDataDescriptor(203, {})));
+  }
+  {
+    // Propagate a regular id.
+    TransformationPropagateInstructionDown transformation(
+        5, 206, {{{21, 207}, {22, 208}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(206));
+    ASSERT_FALSE(transformation_context.GetFactManager()->IdIsIrrelevant(207));
+    ASSERT_TRUE(transformation_context.GetFactManager()->IdIsIrrelevant(208));
+
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(206));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(207));
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(208));
+
+    ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(206, {}), MakeDataDescriptor(207, {})));
+    ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+        MakeDataDescriptor(206, {}), MakeDataDescriptor(208, {})));
+  }
+
+  std::string after_transformation = R"(
+               OpCapability VariablePointers
+          %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
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+         %10 = OpTypePointer Private %6
+         %11 = OpVariable %10 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %23 None
+               OpBranchConditional %13 %21 %22
+         %21 = OpLabel
+        %207 = OpCopyObject %6 %7
+        %204 = OpCopyObject %6 %7 ; Irrelevant id
+        %201 = OpCopyObject %10 %11 ; Pointee is irrelevant
+               OpStore %201 %207
+               OpBranch %23
+         %22 = OpLabel ; Dead block
+        %208 = OpCopyObject %6 %7
+        %205 = OpCopyObject %6 %7 ; Irrelevant id
+        %202 = OpCopyObject %10 %11 ; Pointee is irrelevant
+               OpStore %202 %208
+               OpBranch %23
+         %23 = OpLabel
+        %206 = OpPhi %6 %207 %21 %208 %22
+        %203 = OpPhi %6 %204 %21 %205 %22 ; Irrelevant id
+        %200 = OpPhi %10 %201 %21 %202 %22 ; Pointee is irrelevant
+               OpStore %200 %206
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionDownTest, TestLoops1) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %20
+
+         %20 = OpLabel
+               OpLoopMerge %26 %25 None
+               OpBranch %21
+
+         %21 = OpLabel
+         %22 = OpCopyObject %6 %7
+         %31 = OpCopyObject %6 %7
+               OpSelectionMerge %35 None
+               OpBranchConditional %13 %23 %24
+
+         %23 = OpLabel
+         %27 = OpCopyObject %6 %22
+         %32 = OpCopyObject %6 %31
+               OpBranch %26
+         %24 = OpLabel
+         %28 = OpCopyObject %6 %22
+         %33 = OpCopyObject %6 %31
+               OpBranchConditional %13 %26 %25
+
+         %35 = OpLabel
+               OpBranch %25
+
+         %25 = OpLabel
+         %29 = OpCopyObject %6 %22
+         %34 = OpCopyObject %6 %31
+               OpBranch %20
+         %26 = OpLabel
+         %30 = OpCopyObject %6 %22
+               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);
+
+  {
+    TransformationPropagateInstructionDown transformation(
+        21, 200, {{{23, 201}, {24, 202}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  // Can't replace usage of %22 in %26.
+  ASSERT_FALSE(
+      TransformationPropagateInstructionDown(21, 200, {{{23, 201}, {24, 202}}})
+          .IsApplicable(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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %20
+
+         %20 = OpLabel
+               OpLoopMerge %26 %25 None
+               OpBranch %21
+
+         %21 = OpLabel
+         %22 = OpCopyObject %6 %7
+               OpSelectionMerge %35 None
+               OpBranchConditional %13 %23 %24
+         %23 = OpLabel
+        %201 = OpCopyObject %6 %7
+         %27 = OpCopyObject %6 %22
+         %32 = OpCopyObject %6 %201
+               OpBranch %26
+         %24 = OpLabel
+        %202 = OpCopyObject %6 %7
+         %28 = OpCopyObject %6 %22
+         %33 = OpCopyObject %6 %202
+               OpBranchConditional %13 %26 %25
+
+         %35 = OpLabel
+               OpBranch %25
+
+         %25 = OpLabel
+         %29 = OpCopyObject %6 %22
+         %34 = OpCopyObject %6 %202
+               OpBranch %20
+         %26 = OpLabel
+         %30 = OpCopyObject %6 %22
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionDownTest, TestLoops2) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %20
+
+         %20 = OpLabel
+         %23 = OpPhi %6 %7 %5 %24 %21
+               OpLoopMerge %22 %21 None
+               OpBranch %21
+
+         %21 = OpLabel
+         %24 = OpCopyObject %6 %23
+         %25 = OpCopyObject %6 %7
+               OpBranchConditional %13 %22 %20
+
+         %22 = 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);
+
+  {
+    // Can propagate %25 from %21 into %20.
+    TransformationPropagateInstructionDown transformation(
+        21, 200, {{{20, 201}, {22, 202}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    // Can propagate %201 from %20 into %21.
+    TransformationPropagateInstructionDown transformation(20, 200,
+                                                          {{{21, 203}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  // Can't propagate %24 from %21 into %20.
+  ASSERT_FALSE(
+      TransformationPropagateInstructionDown(21, 200, {{{20, 204}, {22, 205}}})
+          .IsApplicable(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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %20
+
+         %20 = OpLabel
+         %23 = OpPhi %6 %7 %5 %24 %21
+               OpLoopMerge %22 %21 None
+               OpBranch %21
+
+         %21 = OpLabel
+        %203 = OpCopyObject %6 %7
+         %24 = OpCopyObject %6 %23
+               OpBranchConditional %13 %22 %20
+
+         %22 = OpLabel
+        %200 = OpPhi %6 %203 %21
+        %202 = OpCopyObject %6 %7
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionDownTest, TestLoops3) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %20
+
+         %20 = OpLabel
+         %27 = OpPhi %6 %7 %5 %26 %20
+         %25 = OpCopyObject %6 %7
+         %26 = OpCopyObject %6 %7
+               OpLoopMerge %22 %20 None
+               OpBranchConditional %13 %20 %22
+
+         %22 = 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);
+
+  {
+    // Propagate %25 into %20 and %22. Not that we are skipping %26 since not
+    // all of its users are in different blocks (%27).h
+    TransformationPropagateInstructionDown transformation(
+        20, 200, {{{20, 201}, {22, 202}}});
+    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
+          %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
+          %7 = OpConstant %6 1
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %20
+
+         %20 = OpLabel
+         %27 = OpPhi %6 %7 %5 %26 %20
+        %201 = OpCopyObject %6 %7
+         %26 = OpCopyObject %6 %7
+               OpLoopMerge %22 %20 None
+               OpBranchConditional %13 %20 %22
+
+         %22 = OpLabel
+        %202 = OpCopyObject %6 %7
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_propagate_instruction_up_test.cpp b/test/fuzz/transformation_propagate_instruction_up_test.cpp
new file mode 100644
index 0000000..8a04270
--- /dev/null
+++ b/test/fuzz/transformation_propagate_instruction_up_test.cpp
@@ -0,0 +1,952 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_propagate_instruction_up.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPropagateInstructionUpTest, 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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+         %27 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %26 = OpVariable %27 Function
+         %13 = OpFOrdEqual %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpFMod %6 %9 %17
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpFAdd %6 %11 %20
+               OpBranch %15
+
+         %15 = OpLabel
+         %21 = OpPhi %6 %18 %14 %22 %19
+         %23 = OpFMul %6 %21 %21
+         %24 = OpFDiv %6 %21 %23
+               OpBranch %25
+
+         %25 = OpLabel
+         %28 = OpPhi %6 %20 %15
+               OpStore %26 %28
+               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);
+  // |block_id| is invalid.
+  ASSERT_FALSE(TransformationPropagateInstructionUp(40, {{}}).IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(TransformationPropagateInstructionUp(26, {{}}).IsApplicable(
+      context.get(), transformation_context));
+
+  // |block_id| has no predecessors.
+  ASSERT_FALSE(TransformationPropagateInstructionUp(5, {{}}).IsApplicable(
+      context.get(), transformation_context));
+
+  // |block_id| has no valid instructions to propagate.
+  ASSERT_FALSE(TransformationPropagateInstructionUp(25, {{{15, 40}}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Not all predecessors have fresh ids.
+  ASSERT_FALSE(TransformationPropagateInstructionUp(15, {{{19, 40}, {40, 41}}})
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Not all ids are fresh.
+  ASSERT_FALSE(
+      TransformationPropagateInstructionUp(15, {{{19, 40}, {14, 14}, {40, 42}}})
+          .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationPropagateInstructionUp(15, {{{19, 19}, {14, 40}, {40, 42}}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Fresh ids have duplicates.
+  ASSERT_FALSE(
+      TransformationPropagateInstructionUp(15, {{{19, 40}, {14, 40}, {19, 41}}})
+          .IsApplicable(context.get(), transformation_context));
+
+  // Valid transformations.
+  {
+    TransformationPropagateInstructionUp transformation(14, {{{5, 40}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    TransformationPropagateInstructionUp transformation(19, {{{5, 41}}});
+    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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+         %27 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %26 = OpVariable %27 Function
+         %13 = OpFOrdEqual %12 %9 %11
+         %40 = OpFMod %6 %9 %17 ; propagated from %14
+         %41 = OpFAdd %6 %11 %20 ; propagated from %19
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpPhi %6 %40 %5 ; propagated into %5
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpPhi %6 %41 %5 ; propagated into %5
+               OpBranch %15
+
+         %15 = OpLabel
+         %21 = OpPhi %6 %18 %14 %22 %19
+         %23 = OpFMul %6 %21 %21
+         %24 = OpFDiv %6 %21 %23
+               OpBranch %25
+
+         %25 = OpLabel
+         %28 = OpPhi %6 %20 %15
+               OpStore %26 %28
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  {
+    TransformationPropagateInstructionUp transformation(15,
+                                                        {{{14, 43}, {19, 44}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+         %27 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %26 = OpVariable %27 Function
+         %13 = OpFOrdEqual %12 %9 %11
+         %40 = OpFMod %6 %9 %17
+         %41 = OpFAdd %6 %11 %20
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpPhi %6 %40 %5
+         %43 = OpFMul %6 %18 %18 ; propagated from %15
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpPhi %6 %41 %5
+         %44 = OpFMul %6 %22 %22 ; propagated from %15
+               OpBranch %15
+
+         %15 = OpLabel
+         %23 = OpPhi %6 %43 %14 %44 %19 ; propagated into %14 and %19
+         %21 = OpPhi %6 %18 %14 %22 %19
+         %24 = OpFDiv %6 %21 %23
+               OpBranch %25
+
+         %25 = OpLabel
+         %28 = OpPhi %6 %20 %15
+               OpStore %26 %28
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  {
+    TransformationPropagateInstructionUp transformation(15,
+                                                        {{{14, 45}, {19, 46}}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+         %27 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %26 = OpVariable %27 Function
+         %13 = OpFOrdEqual %12 %9 %11
+         %40 = OpFMod %6 %9 %17
+         %41 = OpFAdd %6 %11 %20
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpPhi %6 %40 %5
+         %43 = OpFMul %6 %18 %18
+         %45 = OpFDiv %6 %18 %43 ; propagated from %15
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpPhi %6 %41 %5
+         %44 = OpFMul %6 %22 %22
+         %46 = OpFDiv %6 %22 %44 ; propagated from %15
+               OpBranch %15
+
+         %15 = OpLabel
+         %24 = OpPhi %6 %45 %14 %46 %19 ; propagated into %14 and %19
+         %23 = OpPhi %6 %43 %14 %44 %19
+         %21 = OpPhi %6 %18 %14 %22 %19
+               OpBranch %25
+
+         %25 = OpLabel
+         %28 = OpPhi %6 %20 %15
+               OpStore %26 %28
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionUpTest, BlockDominatesPredecessor1) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %13 = OpFOrdEqual %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpFMod %6 %9 %17
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpFAdd %6 %11 %20
+               OpBranch %15
+
+         %15 = OpLabel ; dominates %26
+         %21 = OpPhi %6 %18 %14 %22 %19 %28 %26
+         %23 = OpFMul %6 %21 %21
+         %24 = OpFDiv %6 %21 %23
+               OpLoopMerge %27 %26 None
+               OpBranch %26
+
+         %26 = OpLabel
+         %28 = OpFAdd %6 %24 %23
+               OpBranch %15
+
+         %27 = 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);
+  TransformationPropagateInstructionUp transformation(
+      15, {{{14, 40}, {19, 41}, {26, 42}}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %13 = OpFOrdEqual %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpFMod %6 %9 %17
+         %40 = OpFMul %6 %18 %18 ; propagated from %15
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpFAdd %6 %11 %20
+         %41 = OpFMul %6 %22 %22 ; propagated from %15
+               OpBranch %15
+
+         %15 = OpLabel
+         %23 = OpPhi %6 %40 %14 %41 %19 %42 %26 ; propagated into %14, %19, %26
+         %21 = OpPhi %6 %18 %14 %22 %19 %28 %26
+         %24 = OpFDiv %6 %21 %23
+               OpLoopMerge %27 %26 None
+               OpBranch %26
+
+         %26 = OpLabel
+         %28 = OpFAdd %6 %24 %23
+         %42 = OpFMul %6 %28 %28 ; propagated from %15
+               OpBranch %15
+
+         %27 = OpLabel
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionUpTest, BlockDominatesPredecessor2) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %13 = OpFOrdEqual %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpFMod %6 %9 %17
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpFAdd %6 %11 %20
+               OpBranch %15
+
+         %15 = OpLabel ; doesn't dominate %26
+         %21 = OpPhi %6 %18 %14 %22 %19 %20 %26
+         %23 = OpFMul %6 %21 %21
+         %24 = OpFDiv %6 %21 %23
+               OpLoopMerge %27 %26 None
+               OpBranch %27
+
+         %26 = OpLabel
+               OpBranch %15
+
+         %27 = 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);
+  TransformationPropagateInstructionUp transformation(
+      15, {{{14, 40}, {19, 41}, {26, 42}}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %13 = OpFOrdEqual %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpFMod %6 %9 %17
+         %40 = OpFMul %6 %18 %18 ; propagated from %15
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpFAdd %6 %11 %20
+         %41 = OpFMul %6 %22 %22 ; propagated from %15
+               OpBranch %15
+
+         %15 = OpLabel
+         %23 = OpPhi %6 %40 %14 %41 %19 %42 %26 ; propagated into %14, %19, %26
+         %21 = OpPhi %6 %18 %14 %22 %19 %20 %26
+         %24 = OpFDiv %6 %21 %23
+               OpLoopMerge %27 %26 None
+               OpBranch %27
+
+         %26 = OpLabel
+         %42 = OpFMul %6 %20 %20 ; propagated from %15
+               OpBranch %15
+
+         %27 = OpLabel
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionUpTest, BlockDominatesPredecessor3) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %13 = OpFOrdEqual %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpFMod %6 %9 %17
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpFAdd %6 %11 %20
+               OpBranch %15
+
+         %15 = OpLabel ; branches to itself
+         %21 = OpPhi %6 %18 %14 %22 %19 %24 %15
+         %23 = OpFMul %6 %21 %21
+         %24 = OpFDiv %6 %21 %23
+               OpLoopMerge %27 %15 None
+               OpBranch %15
+
+         %27 = 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);
+  TransformationPropagateInstructionUp transformation(
+      15, {{{14, 40}, {19, 41}, {15, 42}}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 3.5
+         %11 = OpConstant %6 3.4000001
+         %12 = OpTypeBool
+         %17 = OpConstant %6 4
+         %20 = OpConstant %6 45
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %13 = OpFOrdEqual %12 %9 %11
+               OpSelectionMerge %15 None
+               OpBranchConditional %13 %14 %19
+
+         %14 = OpLabel
+         %18 = OpFMod %6 %9 %17
+         %40 = OpFMul %6 %18 %18 ; propagated from %15
+               OpBranch %15
+
+         %19 = OpLabel
+         %22 = OpFAdd %6 %11 %20
+         %41 = OpFMul %6 %22 %22 ; propagated from %15
+               OpBranch %15
+
+         %15 = OpLabel
+         %23 = OpPhi %6 %40 %14 %41 %19 %42 %15 ; propagated into %14, %19, %15
+         %21 = OpPhi %6 %18 %14 %22 %19 %24 %15
+         %24 = OpFDiv %6 %21 %23
+         %42 = OpFMul %6 %24 %24 ; propagated from %15
+               OpLoopMerge %27 %15 None
+               OpBranch %15
+
+         %27 = OpLabel
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionUpTest,
+     HandlesVariablePointersCapability) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %11 = OpConstant %6 23
+          %7 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %9
+
+          %9 = OpLabel
+         %10 = OpCopyObject %7 %8
+               OpStore %10 %11
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Required capabilities haven't yet been specified.
+  TransformationPropagateInstructionUp transformation(9, {{{5, 40}}});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  context->AddCapability(SpvCapabilityVariablePointers);
+
+  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 VariablePointers
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %11 = OpConstant %6 23
+          %7 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %40 = OpCopyObject %7 %8 ; propagated from %9
+               OpBranch %9
+
+          %9 = OpLabel
+         %10 = OpPhi %7 %40 %5 ; propagated into %5
+               OpStore %10 %11
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionUpTest,
+     HandlesVariablePointersStorageBufferCapability) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %11 = OpConstant %6 23
+          %7 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+               OpBranch %9
+
+          %9 = OpLabel
+         %10 = OpCopyObject %7 %8
+               OpStore %10 %11
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  spvtools::ValidatorOptions validator_options;
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Required capabilities haven't yet been specified
+  TransformationPropagateInstructionUp transformation(9, {{{5, 40}}});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  context->AddCapability(SpvCapabilityVariablePointersStorageBuffer);
+
+  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 VariablePointersStorageBuffer
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %11 = OpConstant %6 23
+          %7 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %40 = OpCopyObject %7 %8 ; propagated from %9
+               OpBranch %9
+
+          %9 = OpLabel
+         %10 = OpPhi %7 %40 %5 ; propagated into %5
+               OpStore %10 %11
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionUpTest, MultipleIdenticalPredecessors) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %11 = OpConstant %6 23
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %13 %9 %9
+
+          %9 = OpLabel
+         %14 = OpPhi %6 %11 %5
+         %10 = OpCopyObject %6 %14
+               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);
+  TransformationPropagateInstructionUp transformation(9, {{{5, 40}}});
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+         %11 = OpConstant %6 23
+         %12 = OpTypeBool
+         %13 = OpConstantTrue %12
+          %4 = OpFunction %2 None %3
+
+          %5 = OpLabel
+         %40 = OpCopyObject %6 %11
+               OpSelectionMerge %9 None
+               OpBranchConditional %13 %9 %9
+
+          %9 = OpLabel
+         %10 = OpPhi %6 %40 %5
+         %14 = OpPhi %6 %11 %5
+               OpReturn
+
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationPropagateInstructionUpTest,
+     InapplicableDueToOpTypeSampledImage) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %10
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %10 RelaxedPrecision
+               OpDecorate %10 DescriptorSet 0
+               OpDecorate %10 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypeImage %6 2D 0 0 0 1 Unknown
+          %8 = OpTypeSampledImage %7
+          %9 = OpTypePointer UniformConstant %8
+         %10 = OpVariable %9 UniformConstant
+         %12 = OpTypeVector %6 2
+         %13 = OpConstant %6 0
+         %14 = OpConstantComposite %12 %13 %13
+         %15 = OpTypeVector %6 4
+         %30 = OpTypeBool
+         %31 = OpConstantTrue %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %11 = OpLoad %8 %10
+               OpSelectionMerge %20 None
+               OpBranchConditional %31 %40 %41
+         %40 = OpLabel
+               OpBranch %20
+         %41 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+         %50 = OpCopyObject %8 %11
+         %16 = OpImageSampleImplicitLod %15 %50 %14
+               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);
+
+  ASSERT_FALSE(
+      TransformationPropagateInstructionUp(20, {{{40, 100}, {41, 101}}})
+          .IsApplicable(context.get(), transformation_context));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_push_id_through_variable_test.cpp b/test/fuzz/transformation_push_id_through_variable_test.cpp
index eac7820..cd45c4c 100644
--- a/test/fuzz/transformation_push_id_through_variable_test.cpp
+++ b/test/fuzz/transformation_push_id_through_variable_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_push_id_through_variable.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -96,13 +98,12 @@
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests the reference shader validity.
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Tests |value_synonym_id| and |variable_id| are fresh ids.
   uint32_t value_id = 21;
@@ -327,11 +328,9 @@
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   uint32_t value_id = 80;
   uint32_t value_synonym_id = 100;
   uint32_t variable_id = 101;
@@ -342,7 +341,7 @@
   auto transformation = TransformationPushIdThroughVariable(
       value_id, value_synonym_id, variable_id, variable_storage_class,
       initializer_id, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   value_id = 21;
   value_synonym_id = 102;
@@ -353,7 +352,7 @@
   transformation = TransformationPushIdThroughVariable(
       value_id, value_synonym_id, variable_id, variable_storage_class,
       initializer_id, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   value_id = 95;
   value_synonym_id = 104;
@@ -364,7 +363,7 @@
   transformation = TransformationPushIdThroughVariable(
       value_id, value_synonym_id, variable_id, variable_storage_class,
       initializer_id, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   value_id = 80;
   value_synonym_id = 106;
@@ -375,7 +374,7 @@
   transformation = TransformationPushIdThroughVariable(
       value_id, value_synonym_id, variable_id, variable_storage_class,
       initializer_id, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   value_id = 21;
   value_synonym_id = 108;
@@ -386,7 +385,7 @@
   transformation = TransformationPushIdThroughVariable(
       value_id, value_synonym_id, variable_id, variable_storage_class,
       initializer_id, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   value_id = 23;
   value_synonym_id = 110;
@@ -397,7 +396,7 @@
   transformation = TransformationPushIdThroughVariable(
       value_id, value_synonym_id, variable_id, variable_storage_class,
       initializer_id, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -486,16 +485,16 @@
   )";
 
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(80, {}),
-                                        MakeDataDescriptor(100, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                        MakeDataDescriptor(102, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(95, {}),
-                                        MakeDataDescriptor(104, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(80, {}),
-                                        MakeDataDescriptor(106, {})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                        MakeDataDescriptor(108, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(80, {}), MakeDataDescriptor(100, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(102, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(95, {}), MakeDataDescriptor(104, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(80, {}), MakeDataDescriptor(106, {})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(108, {})));
 }
 
 TEST(TransformationPushIdThroughVariableTest, AddSynonymsForRelevantIds) {
@@ -573,13 +572,12 @@
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests the reference shader validity.
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   uint32_t value_id = 21;
   uint32_t value_synonym_id = 62;
@@ -593,10 +591,11 @@
       initializer_id, instruction_descriptor);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                        MakeDataDescriptor(62, {})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(62, {})));
 }
 
 TEST(TransformationPushIdThroughVariableTest, DontAddSynonymsForIrrelevantIds) {
@@ -674,15 +673,14 @@
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests the reference shader validity.
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
-  fact_manager.AddFactIdIsIrrelevant(21);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(21);
 
   uint32_t value_id = 21;
   uint32_t value_synonym_id = 62;
@@ -696,10 +694,68 @@
       initializer_id, instruction_descriptor);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(21, {}),
-                                         MakeDataDescriptor(62, {})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(21, {}), MakeDataDescriptor(62, {})));
+}
+
+TEST(TransformationPushIdThroughVariableTest, DontAddSynonymsInDeadBlocks) {
+  std::string reference_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 0
+         %11 = OpConstant %6 1
+         %12 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeBool
+         %50 = OpTypePointer Function %13
+         %14 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+               OpStore %9 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %16
+         %15 = OpLabel
+               OpBranch %16
+         %16 = 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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Tests the reference shader validity.
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(15);
+  auto transformation = TransformationPushIdThroughVariable(
+      14, 100, 101, SpvStorageClassFunction, 14,
+      MakeInstructionDescriptor(15, SpvOpBranch, 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_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(14, {}), MakeDataDescriptor(100, {})));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_record_synonymous_constants_test.cpp b/test/fuzz/transformation_record_synonymous_constants_test.cpp
index 2c309fd..010cb12 100644
--- a/test/fuzz/transformation_record_synonymous_constants_test.cpp
+++ b/test/fuzz/transformation_record_synonymous_constants_test.cpp
@@ -15,6 +15,8 @@
 
 #include "source/fuzz/transformation_record_synonymous_constants.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -27,8 +29,9 @@
 void ApplyTransformationAndCheckFactManager(
     uint32_t constant1_id, uint32_t constant2_id, opt::IRContext* ir_context,
     TransformationContext* transformation_context) {
-  TransformationRecordSynonymousConstants(constant1_id, constant2_id)
-      .Apply(ir_context, transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationRecordSynonymousConstants(constant1_id, constant2_id),
+      ir_context, transformation_context);
 
   ASSERT_TRUE(transformation_context->GetFactManager()->IsSynonymous(
       MakeDataDescriptor(constant1_id, {}),
@@ -84,25 +87,17 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
-#ifndef NDEBUG
   // %3 is not a constant declaration
-  ASSERT_DEATH(TransformationRecordSynonymousConstants(3, 9).IsApplicable(
-                   context.get(), transformation_context),
-               "The ids must refer to constants.");
-#endif
-
-#ifndef NDEBUG
-  // %3 is not a constant declaration
-  ASSERT_DEATH(TransformationRecordSynonymousConstants(9, 3).IsApplicable(
-                   context.get(), transformation_context),
-               "The ids must refer to constants.");
-#endif
+  ASSERT_FALSE(TransformationRecordSynonymousConstants(3, 9).IsApplicable(
+      context.get(), transformation_context));
+  ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 3).IsApplicable(
+      context.get(), transformation_context));
 
   // The two constants must be different
   ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 9).IsApplicable(
@@ -201,11 +196,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %9 and %11 are not equivalent
   ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 11).IsApplicable(
@@ -293,11 +288,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %9 and %13 are not equivalent
   ASSERT_FALSE(TransformationRecordSynonymousConstants(9, 13).IsApplicable(
@@ -399,11 +394,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %25 and %27 are equivalent (25 is zero-like, 27 is null)
   ASSERT_TRUE(TransformationRecordSynonymousConstants(25, 27).IsApplicable(
@@ -536,11 +531,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %29 and %35 are not equivalent (they have different types)
   ASSERT_FALSE(TransformationRecordSynonymousConstants(29, 35).IsApplicable(
@@ -634,11 +629,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %25 and %31 are not equivalent (they have different types)
   ASSERT_FALSE(TransformationRecordSynonymousConstants(25, 31).IsApplicable(
@@ -725,12 +720,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %15 and %17 are not equivalent (having non-equivalent components)
   ASSERT_FALSE(TransformationRecordSynonymousConstants(15, 17).IsApplicable(
@@ -804,17 +798,15 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_TRUE(TransformationRecordSynonymousConstants(7, 8).IsApplicable(
       context.get(), transformation_context));
 
-  fact_manager.AddFactIdIsIrrelevant(7);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(7);
   ASSERT_FALSE(TransformationRecordSynonymousConstants(7, 8).IsApplicable(
       context.get(), transformation_context));
 }
@@ -841,17 +833,15 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_TRUE(TransformationRecordSynonymousConstants(7, 8).IsApplicable(
       context.get(), transformation_context));
 
-  fact_manager.AddFactIdIsIrrelevant(8);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(8);
   ASSERT_FALSE(TransformationRecordSynonymousConstants(7, 8).IsApplicable(
       context.get(), transformation_context));
 }
@@ -877,13 +867,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_FALSE(TransformationRecordSynonymousConstants(7, 8).IsApplicable(
       context.get(), transformation_context));
 
diff --git a/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp b/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp
new file mode 100644
index 0000000..5bc2a8e
--- /dev/null
+++ b/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp
@@ -0,0 +1,618 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     NotApplicableBasicChecks) {
+  // First conditions in IsApplicable() are checked.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "i3"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %11 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %10
+         %14 = OpLoad %6 %8
+         %15 = OpSDiv %6 %13 %14
+               OpStore %12 %15
+               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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Bad: |struct_fresh_id| must be fresh.
+  auto transformation_bad_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(14, 15);
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to an instruction OpSDiv.
+  auto transformation_bad_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(20, 15);
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to an nonexistent instruction.
+  auto transformation_bad_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(20, 21);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     NotApplicableDifferingSignedTypes) {
+  // Operand types and result types do not match. Not applicable to an operation
+  // on vectors with signed integers and operation on signed integers.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "i1"
+               OpName %10 "i2"
+               OpName %16 "v1"
+               OpName %20 "v2"
+               OpName %25 "v3"
+               OpName %31 "u1"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %14 = OpTypeVector %6 3
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %6 0
+         %18 = OpConstant %6 2
+         %19 = OpConstantComposite %14 %17 %9 %18
+         %21 = OpConstant %6 3
+         %22 = OpConstant %6 4
+         %23 = OpConstant %6 5
+         %24 = OpConstantComposite %14 %21 %22 %23
+         %29 = OpTypeInt 32 0
+         %30 = OpTypePointer Function %29
+         %32 = OpConstant %29 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %20 = OpVariable %15 Function
+         %25 = OpVariable %15 Function
+         %31 = OpVariable %30 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+         %12 = OpLoad %6 %8
+         %13 = OpISub %6 %11 %12
+               OpStore %10 %13
+               OpStore %16 %19
+               OpStore %20 %24
+         %26 = OpLoad %14 %16
+         %27 = OpLoad %14 %20
+         %28 = OpIAdd %14 %26 %27
+               OpStore %25 %28
+               OpStore %31 %32
+         %40 = OpIMul %6 %32 %18
+         %41 = OpIAdd %6 %32 %32
+               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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  // Bad: The transformation cannot be applied to an instruction OpIMul that has
+  // different signedness of the types of operands.
+  auto transformation_bad_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 40);
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to an instruction OpIAdd that has
+  // different signedness of the result type than the signedness of the types of
+  // the operands.
+  auto transformation_bad_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 41);
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpIAdd of two
+  // vectors that have signed components.
+  auto transformation_bad_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 28);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpISub of two
+  // signed integers
+  auto transformation_bad_4 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 13);
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     NotApplicableMissingStructTypes) {
+  // In all cases the required struct types are missing.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "u1"
+               OpName %10 "u2"
+               OpName %12 "u3"
+               OpName %24 "i1"
+               OpName %26 "i2"
+               OpName %28 "i3"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %22 = OpTypeInt 32 1
+         %23 = OpTypePointer Function %22
+         %25 = OpConstant %22 1
+         %27 = OpConstant %22 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %24 = OpVariable %23 Function
+         %26 = OpVariable %23 Function
+         %28 = OpVariable %23 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+         %16 = OpLoad %6 %8
+         %17 = OpLoad %6 %10
+         %18 = OpISub %6 %16 %17
+               OpStore %12 %18
+         %19 = OpLoad %6 %8
+         %20 = OpLoad %6 %10
+         %21 = OpIMul %6 %19 %20
+               OpStore %12 %21
+               OpStore %24 %25
+               OpStore %26 %27
+         %29 = OpLoad %22 %24
+         %30 = OpLoad %22 %26
+         %31 = OpIMul %22 %29 %30
+               OpStore %28 %31
+               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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  auto transformation_bad_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 15);
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  auto transformation_bad_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 18);
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpIAdd of two
+  // vectors that have signed components.
+  auto transformation_bad_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 21);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpISub of two
+  // signed integers
+  auto transformation_bad_4 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 31);
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     ApplicableScenarios) {
+  // In this test all of the transformations can be applied. The required struct
+  // types are provided.
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "u1"
+               OpName %10 "u2"
+               OpName %12 "u3"
+               OpName %24 "i1"
+               OpName %26 "i2"
+               OpName %28 "i3"
+               OpName %34 "uv1"
+               OpName %36 "uv2"
+               OpName %39 "uv3"
+               OpName %51 "v1"
+               OpName %53 "v2"
+               OpName %56 "v3"
+               OpName %60 "pair_uint"
+               OpMemberName %60 0 "u_1"
+               OpMemberName %60 1 "u_2"
+               OpName %62 "p_uint"
+               OpName %63 "pair_uvec2"
+               OpMemberName %63 0 "uv_1"
+               OpMemberName %63 1 "uv_2"
+               OpName %65 "p_uvec2"
+               OpName %66 "pair_ivec2"
+               OpMemberName %66 0 "v_1"
+               OpMemberName %66 1 "v_2"
+               OpName %68 "p_ivec2"
+               OpName %69 "pair_int"
+               OpMemberName %69 0 "i_1"
+               OpMemberName %69 1 "i_2"
+               OpName %71 "p_int"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %22 = OpTypeInt 32 1
+         %23 = OpTypePointer Function %22
+         %25 = OpConstant %22 1
+         %27 = OpConstant %22 2
+         %32 = OpTypeVector %6 2
+         %33 = OpTypePointer Function %32
+         %35 = OpConstantComposite %32 %9 %11
+         %37 = OpConstant %6 3
+         %38 = OpConstantComposite %32 %11 %37
+         %49 = OpTypeVector %22 2
+         %50 = OpTypePointer Function %49
+         %52 = OpConstantComposite %49 %25 %27
+         %54 = OpConstant %22 3
+         %55 = OpConstantComposite %49 %27 %54
+         %60 = OpTypeStruct %6 %6
+         %61 = OpTypePointer Private %60
+         %62 = OpVariable %61 Private
+         %63 = OpTypeStruct %32 %32
+         %64 = OpTypePointer Private %63
+         %65 = OpVariable %64 Private
+         %66 = OpTypeStruct %49 %49
+         %67 = OpTypePointer Private %66
+         %68 = OpVariable %67 Private
+         %69 = OpTypeStruct %22 %22
+         %70 = OpTypePointer Private %69
+         %71 = OpVariable %70 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %24 = OpVariable %23 Function
+         %26 = OpVariable %23 Function
+         %28 = OpVariable %23 Function
+         %34 = OpVariable %33 Function
+         %36 = OpVariable %33 Function
+         %39 = OpVariable %33 Function
+         %51 = OpVariable %50 Function
+         %53 = OpVariable %50 Function
+         %56 = OpVariable %50 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+         %16 = OpLoad %6 %8
+         %17 = OpLoad %6 %10
+         %18 = OpISub %6 %16 %17
+               OpStore %12 %18
+         %19 = OpLoad %6 %8
+         %20 = OpLoad %6 %10
+         %21 = OpIMul %6 %19 %20
+               OpStore %12 %21
+               OpStore %24 %25
+               OpStore %26 %27
+         %29 = OpLoad %22 %24
+         %30 = OpLoad %22 %26
+         %31 = OpIMul %22 %29 %30
+               OpStore %28 %31
+               OpStore %34 %35
+               OpStore %36 %38
+         %40 = OpLoad %32 %34
+         %41 = OpLoad %32 %36
+         %42 = OpIAdd %32 %40 %41
+               OpStore %39 %42
+         %43 = OpLoad %32 %34
+         %44 = OpLoad %32 %36
+         %45 = OpISub %32 %43 %44
+               OpStore %39 %45
+         %46 = OpLoad %32 %34
+         %47 = OpLoad %32 %36
+         %48 = OpIMul %32 %46 %47
+               OpStore %39 %48
+               OpStore %51 %52
+               OpStore %53 %55
+         %57 = OpLoad %49 %51
+         %58 = OpLoad %49 %53
+         %59 = OpIMul %49 %57 %58
+               OpStore %56 %59
+               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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  auto transformation_good_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(80, 15);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation_good_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(81, 18);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation_good_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(82, 21);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation_good_4 =
+      TransformationReplaceAddSubMulWithCarryingExtended(83, 31);
+  ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_4, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation_good_5 =
+      TransformationReplaceAddSubMulWithCarryingExtended(84, 42);
+  ASSERT_TRUE(transformation_good_5.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_5, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation_good_6 =
+      TransformationReplaceAddSubMulWithCarryingExtended(85, 45);
+  ASSERT_TRUE(transformation_good_6.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_6, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation_good_7 =
+      TransformationReplaceAddSubMulWithCarryingExtended(86, 48);
+  ASSERT_TRUE(transformation_good_7.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_7, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  auto transformation_good_8 =
+      TransformationReplaceAddSubMulWithCarryingExtended(87, 59);
+  ASSERT_TRUE(transformation_good_8.IsApplicable(context.get(),
+                                                 transformation_context));
+  ApplyAndCheckFreshIds(transformation_good_8, 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 310
+               OpName %4 "main"
+               OpName %8 "u1"
+               OpName %10 "u2"
+               OpName %12 "u3"
+               OpName %24 "i1"
+               OpName %26 "i2"
+               OpName %28 "i3"
+               OpName %34 "uv1"
+               OpName %36 "uv2"
+               OpName %39 "uv3"
+               OpName %51 "v1"
+               OpName %53 "v2"
+               OpName %56 "v3"
+               OpName %60 "pair_uint"
+               OpMemberName %60 0 "u_1"
+               OpMemberName %60 1 "u_2"
+               OpName %62 "p_uint"
+               OpName %63 "pair_uvec2"
+               OpMemberName %63 0 "uv_1"
+               OpMemberName %63 1 "uv_2"
+               OpName %65 "p_uvec2"
+               OpName %66 "pair_ivec2"
+               OpMemberName %66 0 "v_1"
+               OpMemberName %66 1 "v_2"
+               OpName %68 "p_ivec2"
+               OpName %69 "pair_int"
+               OpMemberName %69 0 "i_1"
+               OpMemberName %69 1 "i_2"
+               OpName %71 "p_int"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %22 = OpTypeInt 32 1
+         %23 = OpTypePointer Function %22
+         %25 = OpConstant %22 1
+         %27 = OpConstant %22 2
+         %32 = OpTypeVector %6 2
+         %33 = OpTypePointer Function %32
+         %35 = OpConstantComposite %32 %9 %11
+         %37 = OpConstant %6 3
+         %38 = OpConstantComposite %32 %11 %37
+         %49 = OpTypeVector %22 2
+         %50 = OpTypePointer Function %49
+         %52 = OpConstantComposite %49 %25 %27
+         %54 = OpConstant %22 3
+         %55 = OpConstantComposite %49 %27 %54
+         %60 = OpTypeStruct %6 %6
+         %61 = OpTypePointer Private %60
+         %62 = OpVariable %61 Private
+         %63 = OpTypeStruct %32 %32
+         %64 = OpTypePointer Private %63
+         %65 = OpVariable %64 Private
+         %66 = OpTypeStruct %49 %49
+         %67 = OpTypePointer Private %66
+         %68 = OpVariable %67 Private
+         %69 = OpTypeStruct %22 %22
+         %70 = OpTypePointer Private %69
+         %71 = OpVariable %70 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %24 = OpVariable %23 Function
+         %26 = OpVariable %23 Function
+         %28 = OpVariable %23 Function
+         %34 = OpVariable %33 Function
+         %36 = OpVariable %33 Function
+         %39 = OpVariable %33 Function
+         %51 = OpVariable %50 Function
+         %53 = OpVariable %50 Function
+         %56 = OpVariable %50 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %10
+         %80 = OpIAddCarry %60 %13 %14
+         %15 = OpCompositeExtract %6 %80 0
+               OpStore %12 %15
+         %16 = OpLoad %6 %8
+         %17 = OpLoad %6 %10
+         %81 = OpISubBorrow %60 %16 %17
+         %18 = OpCompositeExtract %6 %81 0
+               OpStore %12 %18
+         %19 = OpLoad %6 %8
+         %20 = OpLoad %6 %10
+         %82 = OpUMulExtended %60 %19 %20
+         %21 = OpCompositeExtract %6 %82 0
+               OpStore %12 %21
+               OpStore %24 %25
+               OpStore %26 %27
+         %29 = OpLoad %22 %24
+         %30 = OpLoad %22 %26
+         %83 = OpSMulExtended %69 %29 %30
+         %31 = OpCompositeExtract %22 %83 0
+               OpStore %28 %31
+               OpStore %34 %35
+               OpStore %36 %38
+         %40 = OpLoad %32 %34
+         %41 = OpLoad %32 %36
+         %84 = OpIAddCarry %63 %40 %41
+         %42 = OpCompositeExtract %32 %84 0
+               OpStore %39 %42
+         %43 = OpLoad %32 %34
+         %44 = OpLoad %32 %36
+         %85 = OpISubBorrow %63 %43 %44
+         %45 = OpCompositeExtract %32 %85 0
+               OpStore %39 %45
+         %46 = OpLoad %32 %34
+         %47 = OpLoad %32 %36
+         %86 = OpUMulExtended %63 %46 %47
+         %48 = OpCompositeExtract %32 %86 0
+               OpStore %39 %48
+               OpStore %51 %52
+               OpStore %53 %55
+         %57 = OpLoad %49 %51
+         %58 = OpLoad %49 %53
+         %87 = OpSMulExtended %66 %57 %58
+         %59 = OpCompositeExtract %49 %87 0
+               OpStore %56 %59
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp b/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp
index 22815e6..b8c2a8a 100644
--- a/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp
+++ b/test/fuzz/transformation_replace_boolean_constant_with_constant_binary_test.cpp
@@ -14,6 +14,7 @@
 
 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
 
+#include "gtest/gtest.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/id_use_descriptor.h"
 #include "source/fuzz/instruction_descriptor.h"
@@ -160,13 +161,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   std::vector<protobufs::IdUseDescriptor> uses_of_true = {
       MakeIdUseDescriptor(41, MakeInstructionDescriptor(44, SpvOpStore, 12), 1),
       MakeIdUseDescriptor(41, MakeInstructionDescriptor(46, SpvOpLogicalOr, 0),
@@ -292,24 +291,28 @@
 
   ASSERT_TRUE(replace_true_with_double_comparison.IsApplicable(
       context.get(), transformation_context));
-  replace_true_with_double_comparison.Apply(context.get(),
-                                            &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replace_true_with_double_comparison, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(replace_true_with_uint32_comparison.IsApplicable(
       context.get(), transformation_context));
-  replace_true_with_uint32_comparison.Apply(context.get(),
-                                            &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replace_true_with_uint32_comparison, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(replace_false_with_float_comparison.IsApplicable(
       context.get(), transformation_context));
-  replace_false_with_float_comparison.Apply(context.get(),
-                                            &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replace_false_with_float_comparison, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(replace_false_with_sint64_comparison.IsApplicable(
       context.get(), transformation_context));
-  replace_false_with_sint64_comparison.Apply(context.get(),
-                                             &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replace_false_with_sint64_comparison, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after = R"(
                OpCapability Shader
@@ -422,7 +425,8 @@
     context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
         context.get(), SpvOpConstant, 6, 200, operands));
     fuzzerutil::UpdateModuleIdBound(context.get(), 200);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     // The transformation is not applicable because %200 is NaN.
     ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                      uses_of_true[0], 11, 200, SpvOpFOrdLessThan, 300)
@@ -438,7 +442,8 @@
     context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
         context.get(), SpvOpConstant, 6, 201, operands));
     fuzzerutil::UpdateModuleIdBound(context.get(), 201);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     // Even though the double constant %11 is less than the infinity %201, the
     // transformation is restricted to only apply to finite values.
     ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
@@ -462,7 +467,8 @@
     context->module()->AddGlobalValue(MakeUnique<opt::Instruction>(
         context.get(), SpvOpConstant, 12, 203, operands));
     fuzzerutil::UpdateModuleIdBound(context.get(), 203);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
     // Even though the negative infinity at %203 is less than the positive
     // infinity %202, the transformation is restricted to only apply to finite
     // values.
@@ -535,13 +541,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto use_of_true_in_if = MakeIdUseDescriptor(
       13, MakeInstructionDescriptor(10, SpvOpBranchConditional, 0), 0);
   auto use_of_false_in_while = MakeIdUseDescriptor(
@@ -554,13 +558,15 @@
 
   ASSERT_TRUE(
       replacement_1.IsApplicable(context.get(), transformation_context));
-  replacement_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement_1, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(
       replacement_2.IsApplicable(context.get(), transformation_context));
-  replacement_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement_2, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after = R"(
                OpCapability Shader
@@ -651,20 +657,18 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor = MakeInstructionDescriptor(14, SpvOpPhi, 0);
   auto id_use_descriptor = MakeIdUseDescriptor(8, instruction_descriptor, 0);
   auto transformation = TransformationReplaceBooleanConstantWithConstantBinary(
       id_use_descriptor, 6, 7, SpvOpULessThan, 15);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -698,7 +702,8 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
@@ -730,13 +735,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_FALSE(TransformationReplaceBooleanConstantWithConstantBinary(
                    MakeIdUseDescriptor(
                        9, MakeInstructionDescriptor(50, SpvOpVariable, 0), 1),
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
new file mode 100644
index 0000000..4532503
--- /dev/null
+++ b/test/fuzz/transformation_replace_branch_from_dead_block_with_exit_test.cpp
@@ -0,0 +1,571 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_branch_from_dead_block_with_exit.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationReplaceBranchFromDeadBlockWithExitTest, 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 = OpTypeBool
+          %7 = OpConstantFalse %6
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 1
+         %17 = OpConstant %12 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpVariable %13 Function
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %8 %21
+          %8 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %7 %10 %16
+         %10 = OpLabel
+               OpStore %14 %15
+               OpBranch %20
+         %20 = OpLabel
+               OpBranch %11
+         %16 = OpLabel
+               OpStore %14 %17
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %9
+         %21 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               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);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(8);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(10);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(11);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(16);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(20);
+
+  // Bad: 4 is not a block
+  ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(4, SpvOpKill, 0)
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: 200 does not exist
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(200, SpvOpKill, 0)
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: 21 is not a dead block
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(21, SpvOpKill, 0)
+          .IsApplicable(context.get(), transformation_context));
+  // Bad: terminator of 8 is not OpBranch
+  ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(8, SpvOpKill, 0)
+                   .IsApplicable(context.get(), transformation_context));
+  // Bad: 10's successor only has 10 as a predecessor
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(10, SpvOpKill, 0)
+          .IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  ASSERT_DEATH(
+      TransformationReplaceBranchFromDeadBlockWithExit(20, SpvOpSwitch, 0)
+          .IsApplicable(context.get(), transformation_context),
+      "Invalid early exit opcode.");
+#endif
+
+  auto transformation1 =
+      TransformationReplaceBranchFromDeadBlockWithExit(20, SpvOpKill, 0);
+  auto transformation2 =
+      TransformationReplaceBranchFromDeadBlockWithExit(16, SpvOpKill, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  // Applying transformation 1 should disable transformation 2
+  ASSERT_FALSE(
+      transformation2.IsApplicable(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
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 1
+         %17 = OpConstant %12 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpVariable %13 Function
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %8 %21
+          %8 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %7 %10 %16
+         %10 = OpLabel
+               OpStore %14 %15
+               OpBranch %20
+         %20 = OpLabel
+               OpKill
+         %16 = OpLabel
+               OpStore %14 %17
+               OpBranch %11
+         %11 = OpLabel
+               OpBranch %9
+         %21 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceBranchFromDeadBlockWithExitTest,
+     VertexShaderWithLoopInContinueConstruct) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main"
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %22 = OpConstant %6 10
+         %23 = OpTypeBool
+         %26 = OpConstant %6 1
+         %40 = OpConstant %6 100
+         %48 = OpConstantFalse %23
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %50 None
+               OpBranchConditional %48 %49 %50
+         %49 = OpLabel
+         %51 = OpFunctionCall %6 %10
+               OpBranch %50
+         %50 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %13 = OpVariable %12 Function
+         %15 = OpVariable %12 Function
+         %33 = OpVariable %12 Function
+               OpStore %33 %14
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %36 %37 None
+               OpBranch %38
+         %38 = OpLabel
+         %39 = OpLoad %6 %33
+         %41 = OpSLessThan %23 %39 %40
+               OpBranchConditional %41 %35 %36
+         %35 = OpLabel
+               OpSelectionMerge %202 None
+               OpBranchConditional %48 %200 %201
+        %200 = OpLabel
+               OpBranch %202
+        %201 = OpLabel
+        %400 = OpCopyObject %6 %14
+               OpBranch %202
+        %202 = OpLabel
+               OpBranch %37
+         %37 = OpLabel
+               OpStore %13 %14
+               OpStore %15 %14
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %18 %19 None
+               OpBranch %20
+         %20 = OpLabel
+               OpSelectionMerge %102 None
+               OpBranchConditional %48 %100 %101
+        %100 = OpLabel
+               OpBranch %102
+        %101 = OpLabel
+               OpBranch %102
+        %102 = OpLabel
+         %21 = OpLoad %6 %15
+         %24 = OpSLessThan %23 %21 %22
+               OpBranchConditional %24 %17 %18
+         %17 = OpLabel
+               OpSelectionMerge %302 None
+               OpBranchConditional %48 %300 %301
+        %300 = OpLabel
+               OpBranch %302
+        %301 = OpLabel
+               OpBranch %302
+        %302 = OpLabel
+         %25 = OpLoad %6 %13
+         %27 = OpIAdd %6 %25 %26
+               OpStore %13 %27
+               OpBranch %19
+         %19 = OpLabel
+         %28 = OpLoad %6 %15
+         %29 = OpIAdd %6 %28 %26
+               OpStore %15 %29
+               OpBranch %16
+         %18 = OpLabel
+         %30 = OpLoad %6 %13
+         %42 = OpCopyObject %6 %30
+         %43 = OpLoad %6 %33
+         %44 = OpIAdd %6 %43 %42
+               OpStore %33 %44
+               OpBranch %34
+         %36 = OpLabel
+         %45 = OpLoad %6 %33
+               OpReturnValue %45
+               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);
+
+  for (auto block : {16, 17,  18,  19,  20,  34,  35,  36,  37,  38,
+                     49, 100, 101, 102, 200, 201, 202, 300, 301, 302}) {
+    transformation_context.GetFactManager()->AddFactBlockIsDead(block);
+  }
+
+  // Bad: OpKill not allowed in vertex shader
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(201, SpvOpKill, 0)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: OpReturn is not allowed in function that expects a returned value.
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(200, SpvOpReturn, 0)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Return value id does not exist
+  ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
+                   201, SpvOpReturnValue, 1000)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Return value id does not have a type
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(200, SpvOpReturnValue, 6)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Return value id does not have the right type
+  ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
+                   201, SpvOpReturnValue, 48)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Return value id is not available
+  ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
+                   200, SpvOpReturnValue, 400)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Early exit now allowed in continue construct
+  ASSERT_FALSE(
+      TransformationReplaceBranchFromDeadBlockWithExit(101, SpvOpUnreachable, 0)
+          .IsApplicable(context.get(), transformation_context));
+
+  // Bad: Early exit now allowed in continue construct (again)
+  ASSERT_FALSE(TransformationReplaceBranchFromDeadBlockWithExit(
+                   300, SpvOpReturnValue, 14)
+                   .IsApplicable(context.get(), transformation_context));
+
+  auto transformation1 = TransformationReplaceBranchFromDeadBlockWithExit(
+      200, SpvOpUnreachable, 0);
+  auto transformation2 = TransformationReplaceBranchFromDeadBlockWithExit(
+      201, SpvOpReturnValue, 400);
+
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_FALSE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  opt::Instruction* return_value_inst =
+      context->get_instr_block(201)->terminator();
+  ASSERT_EQ(SpvOpReturnValue, return_value_inst->opcode());
+  ASSERT_EQ(SPV_OPERAND_TYPE_ID, return_value_inst->GetInOperand(0).type);
+  ASSERT_EQ(400, return_value_inst->GetSingleWordInOperand(0));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %4 "main"
+               OpSource ESSL 320
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeFunction %6
+         %12 = OpTypePointer Function %6
+         %14 = OpConstant %6 0
+         %22 = OpConstant %6 10
+         %23 = OpTypeBool
+         %26 = OpConstant %6 1
+         %40 = OpConstant %6 100
+         %48 = OpConstantFalse %23
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %50 None
+               OpBranchConditional %48 %49 %50
+         %49 = OpLabel
+         %51 = OpFunctionCall %6 %10
+               OpBranch %50
+         %50 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+         %13 = OpVariable %12 Function
+         %15 = OpVariable %12 Function
+         %33 = OpVariable %12 Function
+               OpStore %33 %14
+               OpBranch %34
+         %34 = OpLabel
+               OpLoopMerge %36 %37 None
+               OpBranch %38
+         %38 = OpLabel
+         %39 = OpLoad %6 %33
+         %41 = OpSLessThan %23 %39 %40
+               OpBranchConditional %41 %35 %36
+         %35 = OpLabel
+               OpSelectionMerge %202 None
+               OpBranchConditional %48 %200 %201
+        %200 = OpLabel
+               OpBranch %202
+        %201 = OpLabel
+        %400 = OpCopyObject %6 %14
+               OpReturnValue %400
+        %202 = OpLabel
+               OpBranch %37
+         %37 = OpLabel
+               OpStore %13 %14
+               OpStore %15 %14
+               OpBranch %16
+         %16 = OpLabel
+               OpLoopMerge %18 %19 None
+               OpBranch %20
+         %20 = OpLabel
+               OpSelectionMerge %102 None
+               OpBranchConditional %48 %100 %101
+        %100 = OpLabel
+               OpBranch %102
+        %101 = OpLabel
+               OpBranch %102
+        %102 = OpLabel
+         %21 = OpLoad %6 %15
+         %24 = OpSLessThan %23 %21 %22
+               OpBranchConditional %24 %17 %18
+         %17 = OpLabel
+               OpSelectionMerge %302 None
+               OpBranchConditional %48 %300 %301
+        %300 = OpLabel
+               OpBranch %302
+        %301 = OpLabel
+               OpBranch %302
+        %302 = OpLabel
+         %25 = OpLoad %6 %13
+         %27 = OpIAdd %6 %25 %26
+               OpStore %13 %27
+               OpBranch %19
+         %19 = OpLabel
+         %28 = OpLoad %6 %15
+         %29 = OpIAdd %6 %28 %26
+               OpStore %15 %29
+               OpBranch %16
+         %18 = OpLabel
+         %30 = OpLoad %6 %13
+         %42 = OpCopyObject %6 %30
+         %43 = OpLoad %6 %33
+         %44 = OpIAdd %6 %43 %42
+               OpStore %33 %44
+               OpBranch %34
+         %36 = OpLabel
+         %45 = OpLoad %6 %33
+               OpReturnValue %45
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceBranchFromDeadBlockWithExitTest, OpPhi) {
+  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
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 1
+         %17 = OpConstant %12 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpVariable %13 Function
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %8 %21
+          %8 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %7 %10 %16
+         %10 = OpLabel
+               OpStore %14 %15
+               OpBranch %20
+         %20 = OpLabel
+         %48 = OpCopyObject %12 %15
+               OpBranch %11
+         %16 = OpLabel
+               OpStore %14 %17
+         %49 = OpCopyObject %12 %17
+               OpBranch %11
+         %11 = OpLabel
+         %50 = OpPhi %12 %48 %20 %49 %16
+               OpBranch %9
+         %21 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               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);
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(8);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(10);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(11);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(16);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(20);
+
+  auto transformation1 =
+      TransformationReplaceBranchFromDeadBlockWithExit(20, SpvOpKill, 0);
+  auto transformation2 =
+      TransformationReplaceBranchFromDeadBlockWithExit(16, SpvOpKill, 0);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  // Applying transformation 1 should disable transformation 2
+  ASSERT_FALSE(
+      transformation2.IsApplicable(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
+         %12 = OpTypeInt 32 1
+         %13 = OpTypePointer Function %12
+         %15 = OpConstant %12 1
+         %17 = OpConstant %12 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %14 = OpVariable %13 Function
+               OpSelectionMerge %9 None
+               OpBranchConditional %7 %8 %21
+          %8 = OpLabel
+               OpSelectionMerge %11 None
+               OpBranchConditional %7 %10 %16
+         %10 = OpLabel
+               OpStore %14 %15
+               OpBranch %20
+         %20 = OpLabel
+         %48 = OpCopyObject %12 %15
+               OpKill
+         %16 = OpLabel
+               OpStore %14 %17
+         %49 = OpCopyObject %12 %17
+               OpBranch %11
+         %11 = OpLabel
+         %50 = OpPhi %12 %49 %16
+               OpBranch %9
+         %21 = OpLabel
+               OpBranch %9
+          %9 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
index 8cbba46..fe76068 100644
--- a/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
+++ b/test/fuzz/transformation_replace_constant_with_uniform_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
@@ -22,8 +25,7 @@
 namespace {
 
 bool AddFactHelper(
-    TransformationContext* transformation_context, opt::IRContext* context,
-    uint32_t word,
+    TransformationContext* transformation_context, uint32_t word,
     const protobufs::UniformBufferElementDescriptor& descriptor) {
   protobufs::FactConstantUniform constant_uniform_fact;
   constant_uniform_fact.add_constant_word(word);
@@ -31,7 +33,7 @@
       descriptor;
   protobufs::Fact fact;
   *fact.mutable_constant_uniform_fact() = constant_uniform_fact;
-  return transformation_context->GetFactManager()->AddFact(fact, context);
+  return transformation_context->GetFactManager()->MaybeAddFact(fact);
 }
 
 TEST(TransformationReplaceConstantWithUniformTest, BasicReplacements) {
@@ -102,13 +104,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::UniformBufferElementDescriptor blockname_a =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_b =
@@ -116,12 +116,9 @@
   protobufs::UniformBufferElementDescriptor blockname_c =
       MakeUniformBufferElementDescriptor(0, 0, {2});
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 1, blockname_a));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 2, blockname_b));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 3, blockname_c));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 1, blockname_a));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 2, blockname_b));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 3, blockname_c));
 
   // The constant ids are 9, 11 and 14, for 1, 2 and 3 respectively.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -190,9 +187,10 @@
                    .IsApplicable(context.get(), transformation_context));
 
   // Apply the use of 9 in a store.
-  transformation_use_of_9_in_store.Apply(context.get(),
-                                         &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_use_of_9_in_store, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string after_replacing_use_of_9_in_store = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -245,8 +243,10 @@
   ASSERT_TRUE(transformation_use_of_11_in_add.IsApplicable(
       context.get(), transformation_context));
   // Apply the use of 11 in an add.
-  transformation_use_of_11_in_add.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_use_of_11_in_add, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string after_replacing_use_of_11_in_add = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -301,8 +301,10 @@
   ASSERT_TRUE(transformation_use_of_14_in_add.IsApplicable(
       context.get(), transformation_context));
   // Apply the use of 15 in an add.
-  transformation_use_of_14_in_add.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_use_of_14_in_add, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   std::string after_replacing_use_of_14_in_add = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -468,13 +470,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::UniformBufferElementDescriptor blockname_1 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_2 =
@@ -484,14 +484,10 @@
   protobufs::UniformBufferElementDescriptor blockname_4 =
       MakeUniformBufferElementDescriptor(0, 0, {1, 0, 1, 0});
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 1, blockname_1));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 2, blockname_2));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 3, blockname_3));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 4, blockname_4));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 1, blockname_1));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 2, blockname_2));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 3, blockname_3));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 4, blockname_4));
 
   // The constant ids are 13, 15, 17 and 20, for 1, 2, 3 and 4 respectively.
   protobufs::IdUseDescriptor use_of_13_in_store =
@@ -534,9 +530,10 @@
   ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
       context.get(), transformation_context));
 
-  transformation_use_of_13_in_store.Apply(context.get(),
-                                          &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_use_of_13_in_store, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
       context.get(), transformation_context));
   ASSERT_TRUE(transformation_use_of_15_in_add.IsApplicable(
@@ -546,8 +543,10 @@
   ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
       context.get(), transformation_context));
 
-  transformation_use_of_15_in_add.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_use_of_15_in_add, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
       context.get(), transformation_context));
   ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(
@@ -557,8 +556,10 @@
   ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
       context.get(), transformation_context));
 
-  transformation_use_of_17_in_add.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_use_of_17_in_add, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
       context.get(), transformation_context));
   ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(
@@ -568,9 +569,10 @@
   ASSERT_TRUE(transformation_use_of_20_in_store.IsApplicable(
       context.get(), transformation_context));
 
-  transformation_use_of_20_in_store.Apply(context.get(),
-                                          &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_use_of_20_in_store, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_FALSE(transformation_use_of_13_in_store.IsApplicable(
       context.get(), transformation_context));
   ASSERT_FALSE(transformation_use_of_15_in_add.IsApplicable(
@@ -713,18 +715,15 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::UniformBufferElementDescriptor blockname_0 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 0, blockname_0));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 0, blockname_0));
 
   // The constant id is 9 for 0.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -791,20 +790,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::UniformBufferElementDescriptor blockname_0 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_9 =
       MakeUniformBufferElementDescriptor(0, 0, {1});
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 9, blockname_9));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 9, blockname_9));
 
   // The constant id is 9 for 9.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -868,21 +864,19 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::UniformBufferElementDescriptor blockname_3 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
 
   uint32_t float_data[1];
   float temp = 3.0;
   memcpy(&float_data[0], &temp, sizeof(float));
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_data[0], blockname_3));
+  ASSERT_TRUE(
+      AddFactHelper(&transformation_context, float_data[0], blockname_3));
 
   // The constant id is 9 for 3.0.
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -958,22 +952,18 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::UniformBufferElementDescriptor blockname_9 =
       MakeUniformBufferElementDescriptor(0, 0, {0});
   protobufs::UniformBufferElementDescriptor blockname_10 =
       MakeUniformBufferElementDescriptor(0, 0, {1});
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 9, blockname_9));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 10, blockname_10));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 9, blockname_9));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 10, blockname_10));
 
   // The constant ids for 9 and 10 are 9 and 11 respectively
   protobufs::IdUseDescriptor use_of_9_in_store =
@@ -1177,13 +1167,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   const float float_array_values[5] = {1.0, 1.5, 1.75, 1.875, 1.9375};
   uint32_t float_array_data[5];
   memcpy(&float_array_data, &float_array_values, sizeof(float_array_values));
@@ -1230,43 +1218,35 @@
   protobufs::UniformBufferElementDescriptor uniform_h_y =
       MakeUniformBufferElementDescriptor(0, 0, {2, 1});
 
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_array_data[0], uniform_f_a_0));
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_array_data[1], uniform_f_a_1));
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_array_data[2], uniform_f_a_2));
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_array_data[3], uniform_f_a_3));
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_array_data[4], uniform_f_a_4));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[0],
+                            uniform_f_a_0));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[1],
+                            uniform_f_a_1));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[2],
+                            uniform_f_a_2));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[3],
+                            uniform_f_a_3));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_array_data[4],
+                            uniform_f_a_4));
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 1, uniform_f_b_x));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 2, uniform_f_b_y));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 3, uniform_f_b_z));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 4, uniform_f_b_w));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 1, uniform_f_b_x));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 2, uniform_f_b_y));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 3, uniform_f_b_z));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 4, uniform_f_b_w));
 
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_vector_data[0], uniform_f_c_x));
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_vector_data[1], uniform_f_c_y));
-  ASSERT_TRUE(AddFactHelper(&transformation_context, context.get(),
-                            float_vector_data[2], uniform_f_c_z));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_vector_data[0],
+                            uniform_f_c_x));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_vector_data[1],
+                            uniform_f_c_y));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, float_vector_data[2],
+                            uniform_f_c_z));
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 42, uniform_f_d));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 42, uniform_f_d));
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 22, uniform_g));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 22, uniform_g));
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 100, uniform_h_x));
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 200, uniform_h_y));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 100, uniform_h_x));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 200, uniform_h_y));
 
   std::vector<TransformationReplaceConstantWithUniform> transformations;
 
@@ -1327,8 +1307,10 @@
   for (auto& transformation : transformations) {
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after = R"(
@@ -1528,18 +1510,15 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   protobufs::UniformBufferElementDescriptor blockname_a =
       MakeUniformBufferElementDescriptor(0, 0, {0});
 
-  ASSERT_TRUE(
-      AddFactHelper(&transformation_context, context.get(), 0, blockname_a));
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 0, blockname_a));
 
   ASSERT_FALSE(TransformationReplaceConstantWithUniform(
                    MakeIdUseDescriptor(
@@ -1548,6 +1527,110 @@
                    .IsApplicable(context.get(), transformation_context));
 }
 
+TEST(TransformationReplaceConstantWithUniformTest, ReplaceOpPhiOperand) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %32 DescriptorSet 0
+               OpDecorate %32 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 2
+         %13 = OpConstant %6 4
+         %21 = OpConstant %6 1
+         %34 = OpConstant %6 0
+         %10 = OpTypeBool
+         %30 = OpTypeStruct %6
+         %31 = OpTypePointer Uniform %30
+         %32 = OpVariable %31 Uniform
+         %33 = OpTypePointer Uniform %6
+          %4 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+         %23 = OpPhi %6 %7 %11 %20 %15
+          %9 = OpSLessThan %10 %23 %13
+               OpLoopMerge %8 %15 None
+               OpBranchConditional %9 %15 %8
+         %15 = OpLabel
+         %20 = OpIAdd %6 %23 %21
+               OpBranch %5
+          %8 = 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);
+  auto int_descriptor = MakeUniformBufferElementDescriptor(0, 0, {0});
+
+  ASSERT_TRUE(AddFactHelper(&transformation_context, 2, int_descriptor));
+
+  {
+    TransformationReplaceConstantWithUniform transformation(
+        MakeIdUseDescriptor(7, MakeInstructionDescriptor(23, SpvOpPhi, 0), 0),
+        int_descriptor, 50, 51);
+    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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 320
+               OpDecorate %32 DescriptorSet 0
+               OpDecorate %32 Binding 0
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 2
+         %13 = OpConstant %6 4
+         %21 = OpConstant %6 1
+         %34 = OpConstant %6 0
+         %10 = OpTypeBool
+         %30 = OpTypeStruct %6
+         %31 = OpTypePointer Uniform %30
+         %32 = OpVariable %31 Uniform
+         %33 = OpTypePointer Uniform %6
+          %4 = OpFunction %2 None %3
+         %11 = OpLabel
+         %50 = OpAccessChain %33 %32 %34
+         %51 = OpLoad %6 %50
+               OpBranch %5
+          %5 = OpLabel
+         %23 = OpPhi %6 %51 %11 %20 %15
+          %9 = OpSLessThan %10 %23 %13
+               OpLoopMerge %8 %15 None
+               OpBranchConditional %9 %15 %8
+         %15 = OpLabel
+         %20 = OpIAdd %6 %23 %21
+               OpBranch %5
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp b/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp
index 2bbe605..bcd04af 100644
--- a/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp
+++ b/test/fuzz/transformation_replace_copy_memory_with_load_store_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -65,11 +68,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto instruction_descriptor_invalid_1 =
       MakeInstructionDescriptor(5, SpvOpStore, 0);
@@ -94,15 +97,19 @@
       20, instruction_descriptor_valid_1);
   ASSERT_TRUE(transformation_valid_1.IsApplicable(context.get(),
                                                   transformation_context));
-  transformation_valid_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_valid_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation_valid_2 = TransformationReplaceCopyMemoryWithLoadStore(
       21, instruction_descriptor_valid_2);
   ASSERT_TRUE(transformation_valid_2.IsApplicable(context.get(),
                                                   transformation_context));
-  transformation_valid_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_valid_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp b/test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp
index d1d38a9..fa8c068 100644
--- a/test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp
+++ b/test/fuzz/transformation_replace_copy_object_with_store_load_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_replace_copy_object_with_store_load.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -77,11 +80,11 @@
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Invalid: fresh_variable_id=10 is not fresh.
   auto transformation_invalid_1 = TransformationReplaceCopyObjectWithStoreLoad(
@@ -108,9 +111,10 @@
   ASSERT_FALSE(transformation_invalid_4.IsApplicable(context.get(),
                                                      transformation_context));
 
-  // Invalid: initializer_id=15 is invalid.
+  // Invalid: initializer_id=15 has the wrong type relative to the OpCopyObject
+  // instruction.
   auto transformation_invalid_5 = TransformationReplaceCopyObjectWithStoreLoad(
-      27, 30, SpvStorageClassPrivate, 15);
+      27, 30, SpvStorageClassFunction, 15);
   ASSERT_FALSE(transformation_invalid_5.IsApplicable(context.get(),
                                                      transformation_context));
 
@@ -124,15 +128,19 @@
       27, 30, SpvStorageClassFunction, 9);
   ASSERT_TRUE(transformation_valid_1.IsApplicable(context.get(),
                                                   transformation_context));
-  transformation_valid_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_valid_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation_valid_2 = TransformationReplaceCopyObjectWithStoreLoad(
       28, 32, SpvStorageClassPrivate, 15);
   ASSERT_TRUE(transformation_valid_2.IsApplicable(context.get(),
                                                   transformation_context));
-  transformation_valid_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_valid_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -192,6 +200,68 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationReplaceCopyObjectWithStoreLoad, IrrelevantIdsAndDeadBlocks) {
+  std::string reference_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
+         %30 = OpTypePointer Function %6
+         %10 = OpConstant %6 0
+         %11 = OpConstant %6 1
+         %13 = OpTypeBool
+         %14 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %16
+         %15 = OpLabel
+         %50 = OpCopyObject %6 %10
+               OpBranch %16
+         %16 = OpLabel
+         %51 = OpCopyObject %6 %11
+               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;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  transformation_context.GetFactManager()->AddFactBlockIsDead(15);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(11);
+
+  auto transformation_1 = TransformationReplaceCopyObjectWithStoreLoad(
+      50, 100, SpvStorageClassFunction, 10);
+  ASSERT_TRUE(
+      transformation_1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation_1, context.get(),
+                        &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(100, {}), MakeDataDescriptor(50, {})));
+
+  auto transformation_2 = TransformationReplaceCopyObjectWithStoreLoad(
+      51, 101, SpvStorageClassFunction, 10);
+  ASSERT_TRUE(
+      transformation_2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation_2, context.get(),
+                        &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(101, {}), MakeDataDescriptor(51, {})));
+}
+
 }  // 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 e4a3f00..5c10fc5 100644
--- a/test/fuzz/transformation_replace_id_with_synonym_test.cpp
+++ b/test/fuzz/transformation_replace_id_with_synonym_test.cpp
@@ -13,7 +13,10 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_replace_id_with_synonym.h"
+
+#include "gtest/gtest.h"
 #include "source/fuzz/data_descriptor.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/id_use_descriptor.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
@@ -198,18 +201,18 @@
 }
 
 // Equips the fact manager with synonym facts for the above shader.
-void SetUpIdSynonyms(FactManager* fact_manager, opt::IRContext* context) {
-  fact_manager->AddFact(MakeSynonymFact(15, 200), context);
-  fact_manager->AddFact(MakeSynonymFact(15, 201), context);
-  fact_manager->AddFact(MakeSynonymFact(15, 202), context);
-  fact_manager->AddFact(MakeSynonymFact(55, 203), context);
-  fact_manager->AddFact(MakeSynonymFact(54, 204), context);
-  fact_manager->AddFact(MakeSynonymFact(74, 205), context);
-  fact_manager->AddFact(MakeSynonymFact(78, 206), context);
-  fact_manager->AddFact(MakeSynonymFact(84, 207), context);
-  fact_manager->AddFact(MakeSynonymFact(33, 208), context);
-  fact_manager->AddFact(MakeSynonymFact(12, 209), context);
-  fact_manager->AddFact(MakeSynonymFact(19, 210), context);
+void SetUpIdSynonyms(FactManager* fact_manager) {
+  fact_manager->MaybeAddFact(MakeSynonymFact(15, 200));
+  fact_manager->MaybeAddFact(MakeSynonymFact(15, 201));
+  fact_manager->MaybeAddFact(MakeSynonymFact(15, 202));
+  fact_manager->MaybeAddFact(MakeSynonymFact(55, 203));
+  fact_manager->MaybeAddFact(MakeSynonymFact(54, 204));
+  fact_manager->MaybeAddFact(MakeSynonymFact(74, 205));
+  fact_manager->MaybeAddFact(MakeSynonymFact(78, 206));
+  fact_manager->MaybeAddFact(MakeSynonymFact(84, 207));
+  fact_manager->MaybeAddFact(MakeSynonymFact(33, 208));
+  fact_manager->MaybeAddFact(MakeSynonymFact(12, 209));
+  fact_manager->MaybeAddFact(MakeSynonymFact(19, 210));
 }
 
 TEST(TransformationReplaceIdWithSynonymTest, IllegalTransformations) {
@@ -217,14 +220,12 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, kComplexShader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  SetUpIdSynonyms(transformation_context.GetFactManager(), context.get());
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  SetUpIdSynonyms(transformation_context.GetFactManager());
 
   // %202 cannot replace %15 as in-operand 0 of %300, since %202 does not
   // dominate %300.
@@ -292,22 +293,22 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, kComplexShader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  SetUpIdSynonyms(transformation_context.GetFactManager(), context.get());
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  SetUpIdSynonyms(transformation_context.GetFactManager());
 
   auto global_constant_synonym = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(19, MakeInstructionDescriptor(47, SpvOpStore, 0), 1),
       210);
   ASSERT_TRUE(global_constant_synonym.IsApplicable(context.get(),
                                                    transformation_context));
-  global_constant_synonym.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(global_constant_synonym, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto replace_vector_access_chain_index = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(
@@ -315,9 +316,10 @@
       204);
   ASSERT_TRUE(replace_vector_access_chain_index.IsApplicable(
       context.get(), transformation_context));
-  replace_vector_access_chain_index.Apply(context.get(),
-                                          &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replace_vector_access_chain_index, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // This is an interesting case because it replaces something that is being
   // copied with something that is already a synonym.
@@ -327,23 +329,28 @@
       201);
   ASSERT_TRUE(
       regular_replacement.IsApplicable(context.get(), transformation_context));
-  regular_replacement.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(regular_replacement, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto regular_replacement2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(55, MakeInstructionDescriptor(203, SpvOpStore, 0), 0),
       203);
   ASSERT_TRUE(
       regular_replacement2.IsApplicable(context.get(), transformation_context));
-  regular_replacement2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(regular_replacement2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto good_op_phi = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(74, MakeInstructionDescriptor(86, SpvOpPhi, 0), 2),
       205);
   ASSERT_TRUE(good_op_phi.IsApplicable(context.get(), transformation_context));
-  good_op_phi.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(good_op_phi, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -515,17 +522,15 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(10, 100),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(8, 101),
-                                                   context.get());
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(10, 100));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(8, 101));
 
   // Replace %10 with %100 in:
   // %11 = OpLoad %6 %10
@@ -533,8 +538,9 @@
       MakeIdUseDescriptor(10, MakeInstructionDescriptor(11, SpvOpLoad, 0), 0),
       100);
   ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context));
-  replacement1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement1, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %8 with %101 in:
   // OpStore %8 %11
@@ -542,8 +548,9 @@
       MakeIdUseDescriptor(8, MakeInstructionDescriptor(11, SpvOpStore, 0), 0),
       101);
   ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context));
-  replacement2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement2, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %8 with %101 in:
   // %12 = OpLoad %6 %8
@@ -551,8 +558,9 @@
       MakeIdUseDescriptor(8, MakeInstructionDescriptor(12, SpvOpLoad, 0), 0),
       101);
   ASSERT_TRUE(replacement3.IsApplicable(context.get(), transformation_context));
-  replacement3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement3, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replace %10 with %100 in:
   // OpStore %10 %12
@@ -560,8 +568,9 @@
       MakeIdUseDescriptor(10, MakeInstructionDescriptor(12, SpvOpStore, 0), 0),
       100);
   ASSERT_TRUE(replacement4.IsApplicable(context.get(), transformation_context));
-  replacement4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement4, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -649,15 +658,13 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(14, 100),
-                                                   context.get());
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(14, 100));
 
   // Replace %14 with %100 in:
   // %16 = OpFunctionCall %2 %10 %14
@@ -806,7 +813,7 @@
                OpStore %53 %32
          %56 = OpAccessChain %23 %50 %17 %21 %21 %55
                OpStore %56 %54
-         %58 = OpAccessChain %26 %50 %57 %21 %17
+         %58 = OpInBoundsAccessChain %26 %50 %57 %21 %17
                OpStore %58 %45
                OpReturn
                OpFunctionEnd
@@ -815,41 +822,39 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Add synonym facts corresponding to the OpCopyObject operations that have
   // been applied to all constants in the module.
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(16, 100),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(21, 101),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(17, 102),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(57, 103),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(18, 104),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(40, 105),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(32, 106),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(43, 107),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(55, 108),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(8, 109),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(47, 110),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(28, 111),
-                                                   context.get());
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(45, 112),
-                                                   context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(16, 100));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(21, 101));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(17, 102));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(57, 103));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(18, 104));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(40, 105));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(32, 106));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(43, 107));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(55, 108));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(8, 109));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(47, 110));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(28, 111));
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(45, 112));
 
   // Replacements of the form %16 -> %100
 
@@ -891,8 +896,9 @@
           16, MakeInstructionDescriptor(52, SpvOpAccessChain, 0), 1),
       100);
   ASSERT_TRUE(replacement4.IsApplicable(context.get(), transformation_context));
-  replacement4.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement4, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %52 = OpAccessChain %23 %50 %16 *%16*
   // Corresponds to i[0].*f*
@@ -922,8 +928,9 @@
           16, MakeInstructionDescriptor(53, SpvOpAccessChain, 0), 4),
       100);
   ASSERT_TRUE(replacement7.IsApplicable(context.get(), transformation_context));
-  replacement7.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement7, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replacements of the form %21 -> %101
 
@@ -956,8 +963,9 @@
       101);
   ASSERT_TRUE(
       replacement10.IsApplicable(context.get(), transformation_context));
-  replacement10.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement10, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %44 = OpAccessChain %23 %37 *%21* %21 %43
   // Corresponds to h.*g*.b[0]
@@ -998,8 +1006,9 @@
       101);
   ASSERT_TRUE(
       replacement14.IsApplicable(context.get(), transformation_context));
-  replacement14.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement14, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %53 = OpAccessChain %19 %50 %21 *%21* %16 %16
   // Corresponds to i[1].*g*.a[0]
@@ -1031,12 +1040,12 @@
   ASSERT_FALSE(
       replacement17.IsApplicable(context.get(), transformation_context));
 
-  // %58 = OpAccessChain %26 %50 %57 *%21* %17
+  // %58 = OpInBoundsAccessChain %26 %50 %57 *%21* %17
   // Corresponds to i[3].*g*.c
   // The index %24 used for g cannot be replaced
   auto replacement18 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(
-          21, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 2),
+          21, MakeInstructionDescriptor(58, SpvOpInBoundsAccessChain, 0), 2),
       101);
   ASSERT_FALSE(
       replacement18.IsApplicable(context.get(), transformation_context));
@@ -1052,8 +1061,9 @@
       102);
   ASSERT_TRUE(
       replacement19.IsApplicable(context.get(), transformation_context));
-  replacement19.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement19, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // %27 = OpAccessChain %26 %15 %17
   // Corresponds to d.c
@@ -1084,32 +1094,34 @@
       102);
   ASSERT_TRUE(
       replacement22.IsApplicable(context.get(), transformation_context));
-  replacement22.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement22, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
-  // %58 = OpAccessChain %26 %50 %57 %21 %17
+  // %58 = OpInBoundsAccessChain %26 %50 %57 %21 %17
   // Corresponds to i[3].g.*c*
   // The index %17 used for c cannot be replaced
   auto replacement23 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(
-          17, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 3),
+          17, MakeInstructionDescriptor(58, SpvOpInBoundsAccessChain, 0), 3),
       102);
   ASSERT_FALSE(
       replacement23.IsApplicable(context.get(), transformation_context));
 
   // Replacements of the form %57 -> %103
 
-  // %58 = OpAccessChain %26 %50 *%57* %21 %17
+  // %58 = OpInBoundsAccessChain %26 %50 *%57* %21 %17
   // Corresponds to i[*3*].g.c
   // The index %57 used for 3 *can* be replaced
   auto replacement24 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(
-          57, MakeInstructionDescriptor(58, SpvOpAccessChain, 0), 1),
+          57, MakeInstructionDescriptor(58, SpvOpInBoundsAccessChain, 0), 1),
       103);
   ASSERT_TRUE(
       replacement24.IsApplicable(context.get(), transformation_context));
-  replacement24.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement24, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replacements of the form %32 -> %106
 
@@ -1122,8 +1134,9 @@
       106);
   ASSERT_TRUE(
       replacement25.IsApplicable(context.get(), transformation_context));
-  replacement25.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement25, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replacements of the form %43 -> %107
 
@@ -1136,8 +1149,9 @@
       107);
   ASSERT_TRUE(
       replacement26.IsApplicable(context.get(), transformation_context));
-  replacement26.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement26, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replacements of the form %55 -> %108
 
@@ -1150,8 +1164,9 @@
       108);
   ASSERT_TRUE(
       replacement27.IsApplicable(context.get(), transformation_context));
-  replacement27.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement27, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   // Replacements of the form %8 -> %109
 
@@ -1164,8 +1179,9 @@
       109);
   ASSERT_TRUE(
       replacement28.IsApplicable(context.get(), transformation_context));
-  replacement28.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(replacement28, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -1268,7 +1284,7 @@
                OpStore %53 %32
          %56 = OpAccessChain %23 %50 %102 %21 %21 %108
                OpStore %56 %54
-         %58 = OpAccessChain %26 %50 %103 %21 %17
+         %58 = OpInBoundsAccessChain %26 %50 %103 %21 %17
                OpStore %58 %45
                OpReturn
                OpFunctionEnd
@@ -1316,19 +1332,17 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Add synonym fact relating %50 and %12.
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(50, 12),
-                                                   context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(50, 12));
   // Add synonym fact relating %51 and %14.
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(51, 14),
-                                                   context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(51, 14));
 
   // Not legal because the index being replaced is a struct index.
   ASSERT_FALSE(
@@ -1344,7 +1358,7 @@
           12, MakeInstructionDescriptor(16, SpvOpAccessChain, 0), 2),
       50);
   ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context));
-  replacement1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement1, context.get(), &transformation_context);
 
   // Fine to replace an index into a vector inside the runtime array.
   auto replacement2 = TransformationReplaceIdWithSynonym(
@@ -1352,9 +1366,10 @@
           14, MakeInstructionDescriptor(16, SpvOpAccessChain, 0), 3),
       51);
   ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context));
-  replacement2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement2, context.get(), &transformation_context);
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -1429,16 +1444,14 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Add synonym fact relating %100 and %9.
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(100, 9),
-                                                   context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(100, 9));
 
   // Not legal the Sample argument of OpImageTexelPointer needs to be a zero
   // constant.
@@ -1491,17 +1504,15 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Add synonym fact relating %10 and %13 (equivalent integer constant with
   // different signedness).
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(10, 13),
-                                                   context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(10, 13));
 
   // Legal because OpSNegate always considers the integer as signed
   auto replacement1 = TransformationReplaceIdWithSynonym(
@@ -1509,21 +1520,21 @@
                           0),
       13);
   ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context));
-  replacement1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement1, context.get(), &transformation_context);
 
   // Legal because OpIAdd does not care about the signedness of the operands
   auto replacement2 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(10, MakeInstructionDescriptor(16, SpvOpIAdd, 0), 0),
       13);
   ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context));
-  replacement2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement2, context.get(), &transformation_context);
 
   // Legal because OpSDiv does not care about the signedness of the operands
   auto replacement3 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(10, MakeInstructionDescriptor(17, SpvOpSDiv, 0), 0),
       13);
   ASSERT_TRUE(replacement3.IsApplicable(context.get(), transformation_context));
-  replacement3.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement3, context.get(), &transformation_context);
 
   // Not legal because OpUDiv requires unsigned integers
   ASSERT_FALSE(TransformationReplaceIdWithSynonym(
@@ -1538,7 +1549,7 @@
                           0),
       13);
   ASSERT_TRUE(replacement4.IsApplicable(context.get(), transformation_context));
-  replacement4.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement4, context.get(), &transformation_context);
 
   // Not legal because OpSelect requires both operands to have the same type as
   // the result type
@@ -1556,7 +1567,8 @@
                    13)
                    .IsApplicable(context.get(), transformation_context));
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -1636,24 +1648,22 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Add synonym fact relating %10 and %13 (equivalent integer vectors with
   // different signedness).
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(14, 15),
-                                                   context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(14, 15));
 
   // Legal because OpIAdd does not consider the signedness of the operands
   auto replacement1 = TransformationReplaceIdWithSynonym(
       MakeIdUseDescriptor(14, MakeInstructionDescriptor(18, SpvOpIAdd, 0), 0),
       15);
   ASSERT_TRUE(replacement1.IsApplicable(context.get(), transformation_context));
-  replacement1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement1, context.get(), &transformation_context);
 
   // Not legal because OpStore requires the object to match the type pointed
   // to by the pointer.
@@ -1665,8 +1675,8 @@
 
   // Add synonym fact relating %12 and %13 (equivalent integer constants with
   // different signedness).
-  transformation_context.GetFactManager()->AddFact(MakeSynonymFact(12, 13),
-                                                   context.get());
+  transformation_context.GetFactManager()->MaybeAddFact(
+      MakeSynonymFact(12, 13));
 
   // Legal because the indices of OpAccessChain are always treated as signed
   auto replacement2 = TransformationReplaceIdWithSynonym(
@@ -1674,9 +1684,10 @@
           13, MakeInstructionDescriptor(19, SpvOpAccessChain, 0), 1),
       12);
   ASSERT_TRUE(replacement2.IsApplicable(context.get(), transformation_context));
-  replacement2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(replacement2, context.get(), &transformation_context);
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   const std::string after_transformation = R"(
                OpCapability Shader
@@ -1715,6 +1726,68 @@
   ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
 }
 
+TEST(TransformationReplaceIdWithSynonymTest, IncompatibleTypes) {
+  const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeInt 32 0
+          %9 = OpTypeFloat 32
+         %12 = OpConstant %7 1
+         %13 = OpConstant %8 1
+         %10 = OpConstant %9 1
+          %2 = OpFunction %5 None %6
+         %17 = OpLabel
+         %18 = OpIAdd %7 %12 %13
+         %19 = OpFAdd %9 %10 %10
+               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);
+  auto* op_i_add = context->get_def_use_mgr()->GetDef(18);
+  ASSERT_TRUE(op_i_add);
+
+  auto* op_f_add = context->get_def_use_mgr()->GetDef(19);
+  ASSERT_TRUE(op_f_add);
+
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(13, {}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(12, {}), MakeDataDescriptor(10, {}));
+
+  // Synonym differs only in signedness for OpIAdd.
+  ASSERT_TRUE(TransformationReplaceIdWithSynonym(
+                  MakeIdUseDescriptorFromUse(context.get(), op_i_add, 0), 13)
+                  .IsApplicable(context.get(), transformation_context));
+
+  // Synonym has wrong type for OpIAdd.
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(
+                   MakeIdUseDescriptorFromUse(context.get(), op_i_add, 0), 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Synonym has wrong type for OpFAdd.
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(
+                   MakeIdUseDescriptorFromUse(context.get(), op_f_add, 0), 12)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationReplaceIdWithSynonym(
+                   MakeIdUseDescriptorFromUse(context.get(), op_f_add, 0), 13)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_irrelevant_id_test.cpp b/test/fuzz/transformation_replace_irrelevant_id_test.cpp
new file mode 100644
index 0000000..c04a091
--- /dev/null
+++ b/test/fuzz/transformation_replace_irrelevant_id_test.cpp
@@ -0,0 +1,336 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_irrelevant_id.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+const std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "a"
+               OpName %4 "b"
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpTypeInt 32 1
+         %10 = OpTypePointer Function %9
+         %11 = OpConstant %9 2
+         %12 = OpTypeStruct %9
+         %13 = OpTypeInt 32 0
+         %14 = OpConstant %13 3
+         %15 = OpTypeArray %12 %14
+         %16 = OpTypePointer Function %15
+         %17 = OpConstant %9 0
+          %2 = OpFunction %5 None %6
+         %18 = OpLabel
+          %3 = OpVariable %10 Function
+          %4 = OpVariable %10 Function
+         %19 = OpVariable %16 Function
+               OpStore %3 %11
+         %20 = OpLoad %9 %3
+         %21 = OpAccessChain %10 %19 %20 %17
+         %22 = OpLoad %9 %21
+               OpStore %4 %22
+         %23 = OpLoad %9 %4
+         %24 = OpIAdd %9 %20 %23
+         %25 = OpISub %9 %23 %20
+               OpReturn
+               OpFunctionEnd
+)";
+
+void SetUpIrrelevantIdFacts(FactManager* fact_manager) {
+  fact_manager->AddFactIdIsIrrelevant(17);
+  fact_manager->AddFactIdIsIrrelevant(23);
+  fact_manager->AddFactIdIsIrrelevant(24);
+  fact_manager->AddFactIdIsIrrelevant(25);
+}
+
+TEST(TransformationReplaceIrrelevantIdTest, Inapplicable) {
+  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);
+  SetUpIrrelevantIdFacts(transformation_context.GetFactManager());
+
+  auto instruction_21_descriptor =
+      MakeInstructionDescriptor(21, SpvOpAccessChain, 0);
+  auto instruction_24_descriptor = MakeInstructionDescriptor(24, SpvOpIAdd, 0);
+
+  // %20 has not been declared as irrelevant.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(20, instruction_24_descriptor, 0), 23)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %22 is not used in %24.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(22, instruction_24_descriptor, 1), 20)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Replacement id %50 does not exist.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 50)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %25 is not available to use at %24.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 25)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %24 is not available to use at %24.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 24)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %8 has not the same type as %23.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 8)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %17 is an index to a struct in an access chain, so it can't be replaced.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(17, instruction_21_descriptor, 2), 20)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceIrrelevantIdTest, Apply) {
+  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);
+  SetUpIrrelevantIdFacts(transformation_context.GetFactManager());
+
+  auto instruction_24_descriptor = MakeInstructionDescriptor(24, SpvOpIAdd, 0);
+
+  // Replace the use of %23 in %24 with %22.
+  auto transformation = TransformationReplaceIrrelevantId(
+      MakeIdUseDescriptor(23, instruction_24_descriptor, 1), 22);
+  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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+               OpName %3 "a"
+               OpName %4 "b"
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+          %9 = OpTypeInt 32 1
+         %10 = OpTypePointer Function %9
+         %11 = OpConstant %9 2
+         %12 = OpTypeStruct %9
+         %13 = OpTypeInt 32 0
+         %14 = OpConstant %13 3
+         %15 = OpTypeArray %12 %14
+         %16 = OpTypePointer Function %15
+         %17 = OpConstant %9 0
+          %2 = OpFunction %5 None %6
+         %18 = OpLabel
+          %3 = OpVariable %10 Function
+          %4 = OpVariable %10 Function
+         %19 = OpVariable %16 Function
+               OpStore %3 %11
+         %20 = OpLoad %9 %3
+         %21 = OpAccessChain %10 %19 %20 %17
+         %22 = OpLoad %9 %21
+               OpStore %4 %22
+         %23 = OpLoad %9 %4
+         %24 = OpIAdd %9 %20 %22
+         %25 = OpISub %9 %23 %20
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceIrrelevantIdTest,
+     DoNotReplaceVariableInitializerWithNonConstant) {
+  // Checks that it is not possible to replace the initializer of a variable
+  // with a non-constant id (such as a function parameter).
+  const std::string reference_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 = OpTypeFunction %2 %6
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %2 None %8
+          %9 = OpFunctionParameter %6
+         %11 = OpLabel
+         %12 = OpVariable %7 Function %13
+               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);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(13);
+
+  // We cannot replace the use of %13 in the initializer of %12 with %9 because
+  // %9 is not a constant.
+  ASSERT_FALSE(TransformationReplaceIrrelevantId(
+                   MakeIdUseDescriptor(
+                       13, MakeInstructionDescriptor(12, SpvOpVariable, 0), 1),
+                   9)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceIrrelevantIdTest,
+     DoNotReplaceIrrelevantIdWithOpFunction) {
+  // Checks that an OpFunction result id is not allowed to be used to replace an
+  // irrelevant id.
+  const std::string reference_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 = OpTypeFunction %6
+         %13 = OpConstant %6 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %20 = OpCopyObject %6 %13
+         %21 = OpCopyObject %6 %20
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %7
+         %11 = OpLabel
+               OpReturnValue %13
+               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);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(20);
+
+  // We cannot replace the use of %20 in by %21 with %10 because %10 is an
+  // OpFunction instruction.
+  ASSERT_FALSE(
+      TransformationReplaceIrrelevantId(
+          MakeIdUseDescriptor(
+              20, MakeInstructionDescriptor(21, SpvOpCopyObject, 0), 0),
+          10)
+          .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceIrrelevantIdTest, OpAccessChainIrrelevantIndex) {
+  // Checks that we can't replace irrelevant index operands in OpAccessChain.
+  const std::string reference_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 0
+         %11 = OpConstant %6 2
+         %13 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+         %12 = OpAccessChain %13 %9 %10
+               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);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(10);
+
+  // We cannot replace the use of %10 in %12 with %11 because %10 is an
+  // irrelevant id.
+  ASSERT_FALSE(
+      TransformationReplaceIrrelevantId(
+          MakeIdUseDescriptor(
+              10, MakeInstructionDescriptor(12, SpvOpAccessChain, 0), 1),
+          11)
+          .IsApplicable(context.get(), transformation_context));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp b/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp
index 0b04e96..8ec5552 100644
--- a/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp
+++ b/test/fuzz/transformation_replace_linear_algebra_instruction_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -67,13 +70,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests linear algebra instructions.
   auto instruction_descriptor = MakeInstructionDescriptor(24, SpvOpDot, 0);
   auto transformation = TransformationReplaceLinearAlgebraInstruction(
@@ -137,6 +138,332 @@
       transformation.IsApplicable(context.get(), transformation_context));
 }
 
+TEST(TransformationReplaceLinearAlgebraInstructionTest, ReplaceOpTranspose) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %54 "main"
+
+; Types
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeFloat 32
+          %5 = OpTypeVector %4 2
+          %6 = OpTypeVector %4 3
+          %7 = OpTypeVector %4 4
+          %8 = OpTypeMatrix %5 2
+          %9 = OpTypeMatrix %5 3
+         %10 = OpTypeMatrix %5 4
+         %11 = OpTypeMatrix %6 2
+         %12 = OpTypeMatrix %6 3
+         %13 = OpTypeMatrix %6 4
+         %14 = OpTypeMatrix %7 2
+         %15 = OpTypeMatrix %7 3
+         %16 = OpTypeMatrix %7 4
+
+; Constant scalars
+         %17 = OpConstant %4 1
+         %18 = OpConstant %4 2
+         %19 = OpConstant %4 3
+         %20 = OpConstant %4 4
+         %21 = OpConstant %4 5
+         %22 = OpConstant %4 6
+         %23 = OpConstant %4 7
+         %24 = OpConstant %4 8
+         %25 = OpConstant %4 9
+         %26 = OpConstant %4 10
+         %27 = OpConstant %4 11
+         %28 = OpConstant %4 12
+         %29 = OpConstant %4 13
+         %30 = OpConstant %4 14
+         %31 = OpConstant %4 15
+         %32 = OpConstant %4 16
+
+; Constant vectors
+         %33 = OpConstantComposite %5 %17 %18
+         %34 = OpConstantComposite %5 %19 %20
+         %35 = OpConstantComposite %5 %21 %22
+         %36 = OpConstantComposite %5 %23 %24
+         %37 = OpConstantComposite %6 %17 %18 %19
+         %38 = OpConstantComposite %6 %20 %21 %22
+         %39 = OpConstantComposite %6 %23 %24 %25
+         %40 = OpConstantComposite %6 %26 %27 %28
+         %41 = OpConstantComposite %7 %17 %18 %19 %20
+         %42 = OpConstantComposite %7 %21 %22 %23 %24
+         %43 = OpConstantComposite %7 %25 %26 %27 %28
+         %44 = OpConstantComposite %7 %29 %30 %31 %32
+
+; Constant matrices
+         %45 = OpConstantComposite %8 %33 %34
+         %46 = OpConstantComposite %9 %33 %34 %35
+         %47 = OpConstantComposite %10 %33 %34 %35 %36
+         %48 = OpConstantComposite %11 %37 %38
+         %49 = OpConstantComposite %12 %37 %38 %39
+         %50 = OpConstantComposite %13 %37 %38 %39 %40
+         %51 = OpConstantComposite %14 %41 %42
+         %52 = OpConstantComposite %15 %41 %42 %43
+         %53 = OpConstantComposite %16 %41 %42 %43 %44
+
+; main function
+         %54 = OpFunction %2 None %3
+         %55 = OpLabel
+
+; Transposing a 2x2 matrix
+         %56 = OpTranspose %8 %45
+
+; Transposing a 2x3 matrix
+         %57 = OpTranspose %11 %46
+
+; Transposing a 2x4 matrix
+         %58 = OpTranspose %14 %47
+
+; Transposing a 3x2 matrix
+         %59 = OpTranspose %9 %48
+
+; Transposing a 3x3 matrix
+         %60 = OpTranspose %12 %49
+
+; Transposing a 3x4 matrix
+         %61 = OpTranspose %15 %50
+
+; Transposing a 4x2 matrix
+         %62 = OpTranspose %10 %51
+
+; Transposing a 4x3 matrix
+         %63 = OpTranspose %13 %52
+
+; Transposing a 4x4 matrix
+         %64 = OpTranspose %16 %53
+               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);
+  auto instruction_descriptor =
+      MakeInstructionDescriptor(56, SpvOpTranspose, 0);
+  auto transformation = TransformationReplaceLinearAlgebraInstruction(
+      {65, 66, 67, 68, 69, 70, 71, 72, 73, 74}, instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  instruction_descriptor = MakeInstructionDescriptor(57, SpvOpTranspose, 0);
+  transformation = TransformationReplaceLinearAlgebraInstruction(
+      {75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88},
+      instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  instruction_descriptor = MakeInstructionDescriptor(58, SpvOpTranspose, 0);
+  transformation = TransformationReplaceLinearAlgebraInstruction(
+      {89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105,
+       106},
+      instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  instruction_descriptor = MakeInstructionDescriptor(59, SpvOpTranspose, 0);
+  transformation = TransformationReplaceLinearAlgebraInstruction(
+      {107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
+       121},
+      instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  instruction_descriptor = MakeInstructionDescriptor(60, SpvOpTranspose, 0);
+  transformation = TransformationReplaceLinearAlgebraInstruction(
+      {122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132,
+       133, 134, 135, 136, 137, 138, 139, 140, 141, 142},
+      instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %54 "main"
+
+; Types
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeFloat 32
+          %5 = OpTypeVector %4 2
+          %6 = OpTypeVector %4 3
+          %7 = OpTypeVector %4 4
+          %8 = OpTypeMatrix %5 2
+          %9 = OpTypeMatrix %5 3
+         %10 = OpTypeMatrix %5 4
+         %11 = OpTypeMatrix %6 2
+         %12 = OpTypeMatrix %6 3
+         %13 = OpTypeMatrix %6 4
+         %14 = OpTypeMatrix %7 2
+         %15 = OpTypeMatrix %7 3
+         %16 = OpTypeMatrix %7 4
+
+; Constant scalars
+         %17 = OpConstant %4 1
+         %18 = OpConstant %4 2
+         %19 = OpConstant %4 3
+         %20 = OpConstant %4 4
+         %21 = OpConstant %4 5
+         %22 = OpConstant %4 6
+         %23 = OpConstant %4 7
+         %24 = OpConstant %4 8
+         %25 = OpConstant %4 9
+         %26 = OpConstant %4 10
+         %27 = OpConstant %4 11
+         %28 = OpConstant %4 12
+         %29 = OpConstant %4 13
+         %30 = OpConstant %4 14
+         %31 = OpConstant %4 15
+         %32 = OpConstant %4 16
+
+; Constant vectors
+         %33 = OpConstantComposite %5 %17 %18
+         %34 = OpConstantComposite %5 %19 %20
+         %35 = OpConstantComposite %5 %21 %22
+         %36 = OpConstantComposite %5 %23 %24
+         %37 = OpConstantComposite %6 %17 %18 %19
+         %38 = OpConstantComposite %6 %20 %21 %22
+         %39 = OpConstantComposite %6 %23 %24 %25
+         %40 = OpConstantComposite %6 %26 %27 %28
+         %41 = OpConstantComposite %7 %17 %18 %19 %20
+         %42 = OpConstantComposite %7 %21 %22 %23 %24
+         %43 = OpConstantComposite %7 %25 %26 %27 %28
+         %44 = OpConstantComposite %7 %29 %30 %31 %32
+
+; Constant matrices
+         %45 = OpConstantComposite %8 %33 %34
+         %46 = OpConstantComposite %9 %33 %34 %35
+         %47 = OpConstantComposite %10 %33 %34 %35 %36
+         %48 = OpConstantComposite %11 %37 %38
+         %49 = OpConstantComposite %12 %37 %38 %39
+         %50 = OpConstantComposite %13 %37 %38 %39 %40
+         %51 = OpConstantComposite %14 %41 %42
+         %52 = OpConstantComposite %15 %41 %42 %43
+         %53 = OpConstantComposite %16 %41 %42 %43 %44
+
+; main function
+         %54 = OpFunction %2 None %3
+         %55 = OpLabel
+
+; Transposing a 2x2 matrix
+         %65 = OpCompositeExtract %5 %45 0
+         %66 = OpCompositeExtract %4 %65 0
+         %67 = OpCompositeExtract %5 %45 1
+         %68 = OpCompositeExtract %4 %67 0
+         %69 = OpCompositeConstruct %5 %66 %68
+         %70 = OpCompositeExtract %5 %45 0
+         %71 = OpCompositeExtract %4 %70 1
+         %72 = OpCompositeExtract %5 %45 1
+         %73 = OpCompositeExtract %4 %72 1
+         %74 = OpCompositeConstruct %5 %71 %73
+         %56 = OpCompositeConstruct %8 %69 %74
+
+; Transposing a 2x3 matrix
+         %75 = OpCompositeExtract %5 %46 0
+         %76 = OpCompositeExtract %4 %75 0
+         %77 = OpCompositeExtract %5 %46 1
+         %78 = OpCompositeExtract %4 %77 0
+         %79 = OpCompositeExtract %5 %46 2
+         %80 = OpCompositeExtract %4 %79 0
+         %81 = OpCompositeConstruct %6 %76 %78 %80
+         %82 = OpCompositeExtract %5 %46 0
+         %83 = OpCompositeExtract %4 %82 1
+         %84 = OpCompositeExtract %5 %46 1
+         %85 = OpCompositeExtract %4 %84 1
+         %86 = OpCompositeExtract %5 %46 2
+         %87 = OpCompositeExtract %4 %86 1
+         %88 = OpCompositeConstruct %6 %83 %85 %87
+         %57 = OpCompositeConstruct %11 %81 %88
+
+; Transposing a 2x4 matrix
+         %89 = OpCompositeExtract %5 %47 0
+         %90 = OpCompositeExtract %4 %89 0
+         %91 = OpCompositeExtract %5 %47 1
+         %92 = OpCompositeExtract %4 %91 0
+         %93 = OpCompositeExtract %5 %47 2
+         %94 = OpCompositeExtract %4 %93 0
+         %95 = OpCompositeExtract %5 %47 3
+         %96 = OpCompositeExtract %4 %95 0
+         %97 = OpCompositeConstruct %7 %90 %92 %94 %96
+         %98 = OpCompositeExtract %5 %47 0
+         %99 = OpCompositeExtract %4 %98 1
+        %100 = OpCompositeExtract %5 %47 1
+        %101 = OpCompositeExtract %4 %100 1
+        %102 = OpCompositeExtract %5 %47 2
+        %103 = OpCompositeExtract %4 %102 1
+        %104 = OpCompositeExtract %5 %47 3
+        %105 = OpCompositeExtract %4 %104 1
+        %106 = OpCompositeConstruct %7 %99 %101 %103 %105
+         %58 = OpCompositeConstruct %14 %97 %106
+
+; Transposing a 3x2 matrix
+        %107 = OpCompositeExtract %6 %48 0
+        %108 = OpCompositeExtract %4 %107 0
+        %109 = OpCompositeExtract %6 %48 1
+        %110 = OpCompositeExtract %4 %109 0
+        %111 = OpCompositeConstruct %5 %108 %110
+        %112 = OpCompositeExtract %6 %48 0
+        %113 = OpCompositeExtract %4 %112 1
+        %114 = OpCompositeExtract %6 %48 1
+        %115 = OpCompositeExtract %4 %114 1
+        %116 = OpCompositeConstruct %5 %113 %115
+        %117 = OpCompositeExtract %6 %48 0
+        %118 = OpCompositeExtract %4 %117 2
+        %119 = OpCompositeExtract %6 %48 1
+        %120 = OpCompositeExtract %4 %119 2
+        %121 = OpCompositeConstruct %5 %118 %120
+         %59 = OpCompositeConstruct %9 %111 %116 %121
+
+; Transposing a 3x3 matrix
+        %122 = OpCompositeExtract %6 %49 0
+        %123 = OpCompositeExtract %4 %122 0
+        %124 = OpCompositeExtract %6 %49 1
+        %125 = OpCompositeExtract %4 %124 0
+        %126 = OpCompositeExtract %6 %49 2
+        %127 = OpCompositeExtract %4 %126 0
+        %128 = OpCompositeConstruct %6 %123 %125 %127
+        %129 = OpCompositeExtract %6 %49 0
+        %130 = OpCompositeExtract %4 %129 1
+        %131 = OpCompositeExtract %6 %49 1
+        %132 = OpCompositeExtract %4 %131 1
+        %133 = OpCompositeExtract %6 %49 2
+        %134 = OpCompositeExtract %4 %133 1
+        %135 = OpCompositeConstruct %6 %130 %132 %134
+        %136 = OpCompositeExtract %6 %49 0
+        %137 = OpCompositeExtract %4 %136 2
+        %138 = OpCompositeExtract %6 %49 1
+        %139 = OpCompositeExtract %4 %138 2
+        %140 = OpCompositeExtract %6 %49 2
+        %141 = OpCompositeExtract %4 %140 2
+        %142 = OpCompositeConstruct %6 %137 %139 %141
+         %60 = OpCompositeConstruct %12 %128 %135 %142
+
+; Transposing a 3x4 matrix
+         %61 = OpTranspose %15 %50
+
+; Transposing a 4x2 matrix
+         %62 = OpTranspose %10 %51
+
+; Transposing a 4x3 matrix
+         %63 = OpTranspose %13 %52
+
+; Transposing a 4x4 matrix
+         %64 = OpTranspose %16 %53
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
 TEST(TransformationReplaceLinearAlgebraInstructionTest,
      ReplaceOpVectorTimesScalar) {
   std::string reference_shader = R"(
@@ -173,30 +500,28 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor =
       MakeInstructionDescriptor(17, SpvOpVectorTimesScalar, 0);
   auto transformation = TransformationReplaceLinearAlgebraInstruction(
       {20, 21, 22, 23}, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(18, SpvOpVectorTimesScalar, 0);
   transformation = TransformationReplaceLinearAlgebraInstruction(
       {24, 25, 26, 27, 28, 29}, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(19, SpvOpVectorTimesScalar, 0);
   transformation = TransformationReplaceLinearAlgebraInstruction(
       {30, 31, 32, 33, 34, 35, 36, 37}, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -246,7 +571,8 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
@@ -345,25 +671,23 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor =
       MakeInstructionDescriptor(56, SpvOpMatrixTimesScalar, 0);
   auto transformation = TransformationReplaceLinearAlgebraInstruction(
       {65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76}, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(57, SpvOpMatrixTimesScalar, 0);
   transformation = TransformationReplaceLinearAlgebraInstruction(
       {77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(58, SpvOpMatrixTimesScalar, 0);
@@ -371,7 +695,7 @@
       {95,  96,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106,
        107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -520,7 +844,8 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
@@ -633,19 +958,17 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor =
       MakeInstructionDescriptor(56, SpvOpVectorTimesMatrix, 0);
   auto transformation = TransformationReplaceLinearAlgebraInstruction(
       {65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(57, SpvOpVectorTimesMatrix, 0);
@@ -653,7 +976,7 @@
       {79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
        89, 90, 91, 92, 93, 94, 95, 96, 97, 98},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(58, SpvOpVectorTimesMatrix, 0);
@@ -661,7 +984,7 @@
       {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},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(59, SpvOpVectorTimesMatrix, 0);
@@ -669,7 +992,7 @@
       {125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135,
        136, 137, 138, 139, 140, 141, 142, 143, 144, 145},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -855,7 +1178,8 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
@@ -968,19 +1292,17 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor =
       MakeInstructionDescriptor(56, SpvOpMatrixTimesVector, 0);
   auto transformation = TransformationReplaceLinearAlgebraInstruction(
       {65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(57, SpvOpMatrixTimesVector, 0);
@@ -988,7 +1310,7 @@
       {79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
        97},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(58, SpvOpMatrixTimesVector, 0);
@@ -996,7 +1318,7 @@
       {98,  99,  100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
        110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(59, SpvOpMatrixTimesVector, 0);
@@ -1004,7 +1326,7 @@
       {122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132,
        133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -1188,7 +1510,8 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
@@ -1355,13 +1678,11 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor =
       MakeInstructionDescriptor(56, SpvOpMatrixTimesMatrix, 0);
   auto transformation = TransformationReplaceLinearAlgebraInstruction(
@@ -1369,7 +1690,7 @@
        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},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(57, SpvOpMatrixTimesMatrix, 0);
@@ -1380,7 +1701,7 @@
        159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170,
        171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor =
       MakeInstructionDescriptor(58, SpvOpMatrixTimesMatrix, 0);
@@ -1392,7 +1713,7 @@
        239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252,
        253, 254, 255, 256, 257, 258, 259, 260, 261, 262},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -1731,7 +2052,297 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+TEST(TransformationReplaceLinearAlgebraInstructionTest, ReplaceOpOuterProduct) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %45 "main"
+
+; Types
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeFloat 32
+          %5 = OpTypeVector %4 2
+          %6 = OpTypeVector %4 3
+          %7 = OpTypeVector %4 4
+          %8 = OpTypeMatrix %5 2
+          %9 = OpTypeMatrix %5 3
+         %10 = OpTypeMatrix %5 4
+         %11 = OpTypeMatrix %6 2
+         %12 = OpTypeMatrix %6 3
+         %13 = OpTypeMatrix %6 4
+         %14 = OpTypeMatrix %7 2
+         %15 = OpTypeMatrix %7 3
+         %16 = OpTypeMatrix %7 4
+
+; Constant scalars
+         %17 = OpConstant %4 1
+         %18 = OpConstant %4 2
+         %19 = OpConstant %4 3
+         %20 = OpConstant %4 4
+         %21 = OpConstant %4 5
+         %22 = OpConstant %4 6
+         %23 = OpConstant %4 7
+         %24 = OpConstant %4 8
+         %25 = OpConstant %4 9
+         %26 = OpConstant %4 10
+         %27 = OpConstant %4 11
+         %28 = OpConstant %4 12
+         %29 = OpConstant %4 13
+         %30 = OpConstant %4 14
+         %31 = OpConstant %4 15
+         %32 = OpConstant %4 16
+
+; Constant vectors
+         %33 = OpConstantComposite %5 %17 %18
+         %34 = OpConstantComposite %5 %19 %20
+         %35 = OpConstantComposite %5 %21 %22
+         %36 = OpConstantComposite %5 %23 %24
+         %37 = OpConstantComposite %6 %17 %18 %19
+         %38 = OpConstantComposite %6 %20 %21 %22
+         %39 = OpConstantComposite %6 %23 %24 %25
+         %40 = OpConstantComposite %6 %26 %27 %28
+         %41 = OpConstantComposite %7 %17 %18 %19 %20
+         %42 = OpConstantComposite %7 %21 %22 %23 %24
+         %43 = OpConstantComposite %7 %25 %26 %27 %28
+         %44 = OpConstantComposite %7 %29 %30 %31 %32
+
+; main function
+         %45 = OpFunction %2 None %3
+         %46 = OpLabel
+
+; Multiplying 2-dimensional vector by 2-dimensional vector
+         %47 = OpOuterProduct %8 %33 %34
+
+; Multiplying 2-dimensional vector by 3-dimensional vector
+         %48 = OpOuterProduct %9 %35 %37
+
+; Multiplying 2-dimensional vector by 4-dimensional vector
+         %49 = OpOuterProduct %10 %36 %41
+
+; Multiplying 3-dimensional vector by 2-dimensional vector
+         %50 = OpOuterProduct %11 %37 %33
+
+; Multiplying 3-dimensional vector by 3-dimensional vector
+         %51 = OpOuterProduct %12 %38 %39
+
+; Multiplying 3-dimensional vector by 4-dimensional vector
+         %52 = OpOuterProduct %13 %40 %41
+
+; Multiplying 4-dimensional vector by 2-dimensional vector
+         %53 = OpOuterProduct %14 %41 %33
+
+; Multiplying 4-dimensional vector by 3-dimensional vector
+         %54 = OpOuterProduct %15 %42 %37
+
+; Multiplying 4-dimensional vector by 4-dimensional vector
+         %55 = OpOuterProduct %16 %43 %44
+               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);
+  auto instruction_descriptor =
+      MakeInstructionDescriptor(47, SpvOpOuterProduct, 0);
+  auto transformation = TransformationReplaceLinearAlgebraInstruction(
+      {56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67}, instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  instruction_descriptor = MakeInstructionDescriptor(48, SpvOpOuterProduct, 0);
+  transformation = TransformationReplaceLinearAlgebraInstruction(
+      {68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85},
+      instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  instruction_descriptor = MakeInstructionDescriptor(49, SpvOpOuterProduct, 0);
+  transformation = TransformationReplaceLinearAlgebraInstruction(
+      {86, 87, 88,  89,  90,  91,  92,  93,  94,  95,  96,  97,
+       98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109},
+      instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  instruction_descriptor = MakeInstructionDescriptor(50, SpvOpOuterProduct, 0);
+  transformation = TransformationReplaceLinearAlgebraInstruction(
+      {110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
+       124, 125},
+      instruction_descriptor);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %45 "main"
+
+; Types
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeFloat 32
+          %5 = OpTypeVector %4 2
+          %6 = OpTypeVector %4 3
+          %7 = OpTypeVector %4 4
+          %8 = OpTypeMatrix %5 2
+          %9 = OpTypeMatrix %5 3
+         %10 = OpTypeMatrix %5 4
+         %11 = OpTypeMatrix %6 2
+         %12 = OpTypeMatrix %6 3
+         %13 = OpTypeMatrix %6 4
+         %14 = OpTypeMatrix %7 2
+         %15 = OpTypeMatrix %7 3
+         %16 = OpTypeMatrix %7 4
+
+; Constant scalars
+         %17 = OpConstant %4 1
+         %18 = OpConstant %4 2
+         %19 = OpConstant %4 3
+         %20 = OpConstant %4 4
+         %21 = OpConstant %4 5
+         %22 = OpConstant %4 6
+         %23 = OpConstant %4 7
+         %24 = OpConstant %4 8
+         %25 = OpConstant %4 9
+         %26 = OpConstant %4 10
+         %27 = OpConstant %4 11
+         %28 = OpConstant %4 12
+         %29 = OpConstant %4 13
+         %30 = OpConstant %4 14
+         %31 = OpConstant %4 15
+         %32 = OpConstant %4 16
+
+; Constant vectors
+         %33 = OpConstantComposite %5 %17 %18
+         %34 = OpConstantComposite %5 %19 %20
+         %35 = OpConstantComposite %5 %21 %22
+         %36 = OpConstantComposite %5 %23 %24
+         %37 = OpConstantComposite %6 %17 %18 %19
+         %38 = OpConstantComposite %6 %20 %21 %22
+         %39 = OpConstantComposite %6 %23 %24 %25
+         %40 = OpConstantComposite %6 %26 %27 %28
+         %41 = OpConstantComposite %7 %17 %18 %19 %20
+         %42 = OpConstantComposite %7 %21 %22 %23 %24
+         %43 = OpConstantComposite %7 %25 %26 %27 %28
+         %44 = OpConstantComposite %7 %29 %30 %31 %32
+
+; main function
+         %45 = OpFunction %2 None %3
+         %46 = OpLabel
+
+; Multiplying 2-dimensional vector by 2-dimensional vector
+         %56 = OpCompositeExtract %4 %34 0
+         %57 = OpCompositeExtract %4 %33 0
+         %58 = OpFMul %4 %56 %57
+         %59 = OpCompositeExtract %4 %33 1
+         %60 = OpFMul %4 %56 %59
+         %61 = OpCompositeConstruct %5 %58 %60
+         %62 = OpCompositeExtract %4 %34 1
+         %63 = OpCompositeExtract %4 %33 0
+         %64 = OpFMul %4 %62 %63
+         %65 = OpCompositeExtract %4 %33 1
+         %66 = OpFMul %4 %62 %65
+         %67 = OpCompositeConstruct %5 %64 %66
+         %47 = OpCompositeConstruct %8 %61 %67
+
+; Multiplying 2-dimensional vector by 3-dimensional vector
+         %68 = OpCompositeExtract %4 %37 0
+         %69 = OpCompositeExtract %4 %35 0
+         %70 = OpFMul %4 %68 %69
+         %71 = OpCompositeExtract %4 %35 1
+         %72 = OpFMul %4 %68 %71
+         %73 = OpCompositeConstruct %5 %70 %72
+         %74 = OpCompositeExtract %4 %37 1
+         %75 = OpCompositeExtract %4 %35 0
+         %76 = OpFMul %4 %74 %75
+         %77 = OpCompositeExtract %4 %35 1
+         %78 = OpFMul %4 %74 %77
+         %79 = OpCompositeConstruct %5 %76 %78
+         %80 = OpCompositeExtract %4 %37 2
+         %81 = OpCompositeExtract %4 %35 0
+         %82 = OpFMul %4 %80 %81
+         %83 = OpCompositeExtract %4 %35 1
+         %84 = OpFMul %4 %80 %83
+         %85 = OpCompositeConstruct %5 %82 %84
+         %48 = OpCompositeConstruct %9 %73 %79 %85
+
+; Multiplying 2-dimensional vector by 4-dimensional vector
+         %86 = OpCompositeExtract %4 %41 0
+         %87 = OpCompositeExtract %4 %36 0
+         %88 = OpFMul %4 %86 %87
+         %89 = OpCompositeExtract %4 %36 1
+         %90 = OpFMul %4 %86 %89
+         %91 = OpCompositeConstruct %5 %88 %90
+         %92 = OpCompositeExtract %4 %41 1
+         %93 = OpCompositeExtract %4 %36 0
+         %94 = OpFMul %4 %92 %93
+         %95 = OpCompositeExtract %4 %36 1
+         %96 = OpFMul %4 %92 %95
+         %97 = OpCompositeConstruct %5 %94 %96
+         %98 = OpCompositeExtract %4 %41 2
+         %99 = OpCompositeExtract %4 %36 0
+        %100 = OpFMul %4 %98 %99
+        %101 = OpCompositeExtract %4 %36 1
+        %102 = OpFMul %4 %98 %101
+        %103 = OpCompositeConstruct %5 %100 %102
+        %104 = OpCompositeExtract %4 %41 3
+        %105 = OpCompositeExtract %4 %36 0
+        %106 = OpFMul %4 %104 %105
+        %107 = OpCompositeExtract %4 %36 1
+        %108 = OpFMul %4 %104 %107
+        %109 = OpCompositeConstruct %5 %106 %108
+         %49 = OpCompositeConstruct %10 %91 %97 %103 %109
+
+; Multiplying 3-dimensional vector by 2-dimensional vector
+        %110 = OpCompositeExtract %4 %33 0
+        %111 = OpCompositeExtract %4 %37 0
+        %112 = OpFMul %4 %110 %111
+        %113 = OpCompositeExtract %4 %37 1
+        %114 = OpFMul %4 %110 %113
+        %115 = OpCompositeExtract %4 %37 2
+        %116 = OpFMul %4 %110 %115
+        %117 = OpCompositeConstruct %6 %112 %114 %116
+        %118 = OpCompositeExtract %4 %33 1
+        %119 = OpCompositeExtract %4 %37 0
+        %120 = OpFMul %4 %118 %119
+        %121 = OpCompositeExtract %4 %37 1
+        %122 = OpFMul %4 %118 %121
+        %123 = OpCompositeExtract %4 %37 2
+        %124 = OpFMul %4 %118 %123
+        %125 = OpCompositeConstruct %6 %120 %122 %124
+         %50 = OpCompositeConstruct %11 %117 %125
+
+; Multiplying 3-dimensional vector by 3-dimensional vector
+         %51 = OpOuterProduct %12 %38 %39
+
+; Multiplying 3-dimensional vector by 4-dimensional vector
+         %52 = OpOuterProduct %13 %40 %41
+
+; Multiplying 4-dimensional vector by 2-dimensional vector
+         %53 = OpOuterProduct %14 %41 %33
+
+; Multiplying 4-dimensional vector by 3-dimensional vector
+         %54 = OpOuterProduct %15 %42 %37
+
+; Multiplying 4-dimensional vector by 4-dimensional vector
+         %55 = OpOuterProduct %16 %43 %44
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
@@ -1777,28 +2388,26 @@
   const auto consumer = nullptr;
   const auto context =
       BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instruction_descriptor = MakeInstructionDescriptor(24, SpvOpDot, 0);
   auto transformation = TransformationReplaceLinearAlgebraInstruction(
       {27, 28, 29, 30, 31, 32}, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor = MakeInstructionDescriptor(25, SpvOpDot, 0);
   transformation = TransformationReplaceLinearAlgebraInstruction(
       {33, 34, 35, 36, 37, 38, 39, 40, 41, 42}, instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instruction_descriptor = MakeInstructionDescriptor(26, SpvOpDot, 0);
   transformation = TransformationReplaceLinearAlgebraInstruction(
       {43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56},
       instruction_descriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variant_shader = R"(
                OpCapability Shader
@@ -1867,7 +2476,8 @@
                OpFunctionEnd
   )";
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
   ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
 }
 
diff --git a/test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp b/test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp
index aa3d1fc..bc10099 100644
--- a/test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp
+++ b/test/fuzz/transformation_replace_load_store_with_copy_memory_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_replace_load_store_with_copy_memory.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -27,7 +30,7 @@
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
+               OpEntryPoint Fragment %4 "main" %26 %28 %31 %33
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
                OpName %4 "main"
@@ -37,6 +40,12 @@
                OpName %14 "d"
                OpName %18 "e"
                OpName %20 "f"
+               OpName %26 "i1"
+               OpName %28 "i2"
+               OpName %31 "g1"
+               OpName %33 "g2"
+               OpDecorate %26 Location 0
+               OpDecorate %28 Location 1
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeInt 32 1
@@ -49,6 +58,16 @@
          %17 = OpTypePointer Function %16
          %19 = OpConstant %16 2
          %21 = OpConstant %16 3
+         %25 = OpTypePointer Output %6
+         %26 = OpVariable %25 Output
+         %27 = OpConstant %6 1
+         %28 = OpVariable %25 Output
+         %30 = OpTypePointer Private %6
+         %31 = OpVariable %30 Private
+         %32 = OpConstant %6 0
+         %33 = OpVariable %30 Private
+         %35 = OpTypeBool
+         %36 = OpConstantTrue %35
           %4 = OpFunction %2 None %3
           %5 = OpLabel
           %8 = OpVariable %7 Function
@@ -70,6 +89,20 @@
                OpStore %14 %23
          %24 = OpLoad %16 %18
                OpStore %20 %24
+               OpStore %26 %27
+               OpStore %28 %27
+         %29 = OpLoad %6 %26
+               OpMemoryBarrier %32 %32
+               OpStore %28 %29
+               OpStore %31 %32
+               OpStore %33 %32
+         %34 = OpLoad %6 %33
+               OpSelectionMerge %38 None
+               OpBranchConditional %36 %37 %38
+         %37 = OpLabel
+               OpStore %31 %34
+               OpBranch %38
+         %38 = OpLabel
                OpReturn
                OpFunctionEnd
     )";
@@ -78,27 +111,36 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto bad_instruction_descriptor_1 =
-      MakeInstructionDescriptor(5, SpvOpVariable, 0);
+      MakeInstructionDescriptor(11, SpvOpConstant, 0);
 
   auto load_instruction_descriptor_1 =
-      MakeInstructionDescriptor(5, SpvOpLoad, 0);
+      MakeInstructionDescriptor(22, SpvOpLoad, 0);
   auto load_instruction_descriptor_2 =
-      MakeInstructionDescriptor(5, SpvOpLoad, 1);
+      MakeInstructionDescriptor(23, SpvOpLoad, 0);
   auto load_instruction_descriptor_3 =
-      MakeInstructionDescriptor(5, SpvOpLoad, 2);
+      MakeInstructionDescriptor(24, SpvOpLoad, 0);
+  auto load_instruction_descriptor_other_block =
+      MakeInstructionDescriptor(34, SpvOpLoad, 0);
+  auto load_instruction_descriptor_unsafe =
+      MakeInstructionDescriptor(29, SpvOpLoad, 0);
+
   auto store_instruction_descriptor_1 =
       MakeInstructionDescriptor(22, SpvOpStore, 0);
   auto store_instruction_descriptor_2 =
       MakeInstructionDescriptor(23, SpvOpStore, 0);
   auto store_instruction_descriptor_3 =
       MakeInstructionDescriptor(24, SpvOpStore, 0);
+  auto store_instruction_descriptor_other_block =
+      MakeInstructionDescriptor(37, SpvOpStore, 0);
+  auto store_instruction_descriptor_unsafe =
+      MakeInstructionDescriptor(29, SpvOpStore, 0);
 
   // Bad: |load_instruction_descriptor| is incorrect.
   auto transformation_bad_1 = TransformationReplaceLoadStoreWithCopyMemory(
@@ -118,32 +160,50 @@
   ASSERT_FALSE(
       transformation_bad_3.IsApplicable(context.get(), transformation_context));
 
-  // Bad: There is a interfering OpCopyMemory instruction between the OpLoad and
-  // the OpStore.
+  // Bad: There is an interfering OpCopyMemory instruction between the OpLoad
+  // and the OpStore.
   auto transformation_bad_4 = TransformationReplaceLoadStoreWithCopyMemory(
       load_instruction_descriptor_1, store_instruction_descriptor_1);
   ASSERT_FALSE(
       transformation_bad_4.IsApplicable(context.get(), transformation_context));
 
+  // Bad: There is an interfering OpMemoryBarrier instruction between the OpLoad
+  // and the OpStore.
+  auto transformation_bad_5 = TransformationReplaceLoadStoreWithCopyMemory(
+      load_instruction_descriptor_unsafe, store_instruction_descriptor_unsafe);
+  ASSERT_FALSE(
+      transformation_bad_5.IsApplicable(context.get(), transformation_context));
+
+  // Bad: OpLoad and OpStore instructions are in different blocks.
+  auto transformation_bad_6 = TransformationReplaceLoadStoreWithCopyMemory(
+      load_instruction_descriptor_other_block,
+      store_instruction_descriptor_other_block);
+  ASSERT_FALSE(
+      transformation_bad_6.IsApplicable(context.get(), transformation_context));
+
   auto transformation_good_1 = TransformationReplaceLoadStoreWithCopyMemory(
       load_instruction_descriptor_2, store_instruction_descriptor_2);
   ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
                                                  transformation_context));
-  transformation_good_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_good_1, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   auto transformation_good_2 = TransformationReplaceLoadStoreWithCopyMemory(
       load_instruction_descriptor_3, store_instruction_descriptor_3);
   ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
                                                  transformation_context));
-  transformation_good_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(transformation_good_2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_transformations = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
+               OpEntryPoint Fragment %4 "main" %26 %28 %31 %33
                OpExecutionMode %4 OriginUpperLeft
                OpSource ESSL 310
                OpName %4 "main"
@@ -153,6 +213,12 @@
                OpName %14 "d"
                OpName %18 "e"
                OpName %20 "f"
+               OpName %26 "i1"
+               OpName %28 "i2"
+               OpName %31 "g1"
+               OpName %33 "g2"
+               OpDecorate %26 Location 0
+               OpDecorate %28 Location 1
           %2 = OpTypeVoid
           %3 = OpTypeFunction %2
           %6 = OpTypeInt 32 1
@@ -165,6 +231,16 @@
          %17 = OpTypePointer Function %16
          %19 = OpConstant %16 2
          %21 = OpConstant %16 3
+         %25 = OpTypePointer Output %6
+         %26 = OpVariable %25 Output
+         %27 = OpConstant %6 1
+         %28 = OpVariable %25 Output
+         %30 = OpTypePointer Private %6
+         %31 = OpVariable %30 Private
+         %32 = OpConstant %6 0
+         %33 = OpVariable %30 Private
+         %35 = OpTypeBool
+         %36 = OpConstantTrue %35
           %4 = OpFunction %2 None %3
           %5 = OpLabel
           %8 = OpVariable %7 Function
@@ -186,9 +262,23 @@
                OpCopyMemory %14 %12
          %24 = OpLoad %16 %18
                OpCopyMemory %20 %18
+               OpStore %26 %27
+               OpStore %28 %27
+         %29 = OpLoad %6 %26
+               OpMemoryBarrier %32 %32
+               OpStore %28 %29
+               OpStore %31 %32
+               OpStore %33 %32
+         %34 = OpLoad %6 %33
+               OpSelectionMerge %38 None
+               OpBranchConditional %36 %37 %38
+         %37 = OpLabel
+               OpStore %31 %34
+               OpBranch %38
+         %38 = OpLabel
                OpReturn
                OpFunctionEnd
-  )";
+    )";
   ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
 }
 
diff --git a/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp
new file mode 100644
index 0000000..875d79e
--- /dev/null
+++ b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp
@@ -0,0 +1,212 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpConstant %8 2
+         %10 = OpConstant %8 3
+         %11 = OpConstant %8 4
+         %12 = OpConstant %8 5
+         %13 = OpConstant %8 6
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %6 %16 %17
+         %16 = OpLabel
+         %18 = OpCopyObject %8 %9
+               OpSelectionMerge %19 None
+               OpBranchConditional %7 %20 %21
+         %20 = OpLabel
+         %22 = OpCopyObject %8 %10
+         %23 = OpCopyObject %8 %12
+               OpBranch %19
+         %21 = OpLabel
+         %24 = OpCopyObject %8 %9
+               OpBranch %19
+         %19 = OpLabel
+         %25 = OpPhi %8 %22 %20 %24 %21
+               OpBranch %15
+         %17 = OpLabel
+         %26 = OpCopyObject %8 %12
+         %27 = OpCopyObject %8 %13
+               OpBranch %28
+         %28 = OpLabel
+         %29 = OpPhi %8 %27 %17
+               OpBranch %15
+         %15 = OpLabel
+         %30 = OpPhi %8 %25 %19 %26 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Inapplicable) {
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Record the fact that blocks 20, 17, 28 are dead.
+  transformation_context.GetFactManager()->AddFactBlockIsDead(20);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(17);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(28);
+
+  // %26 is not an OpPhi instruction.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(26, 14, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %25 is not a block label.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 25, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %14 is not a predecessor of %28 (which contains %29).
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(29, 14, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %19 is not a dead block.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 19, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %7 does not have the same type id as %25.
+  ASSERT_FALSE(
+      TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 7).IsApplicable(
+          context.get(), transformation_context));
+
+  // %29 is not available at the end of %20.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 29)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Apply) {
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Record the fact that blocks 20, 17, 28 are dead.
+  transformation_context.GetFactManager()->AddFactBlockIsDead(20);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(17);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(28);
+
+  auto transformation1 =
+      TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 18);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+
+  auto transformation2 =
+      TransformationReplaceOpPhiIdFromDeadPredecessor(30, 28, 29);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+
+  auto transformation3 =
+      TransformationReplaceOpPhiIdFromDeadPredecessor(29, 17, 10);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpConstant %8 2
+         %10 = OpConstant %8 3
+         %11 = OpConstant %8 4
+         %12 = OpConstant %8 5
+         %13 = OpConstant %8 6
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %6 %16 %17
+         %16 = OpLabel
+         %18 = OpCopyObject %8 %9
+               OpSelectionMerge %19 None
+               OpBranchConditional %7 %20 %21
+         %20 = OpLabel
+         %22 = OpCopyObject %8 %10
+         %23 = OpCopyObject %8 %12
+               OpBranch %19
+         %21 = OpLabel
+         %24 = OpCopyObject %8 %9
+               OpBranch %19
+         %19 = OpLabel
+         %25 = OpPhi %8 %18 %20 %24 %21
+               OpBranch %15
+         %17 = OpLabel
+         %26 = OpCopyObject %8 %12
+         %27 = OpCopyObject %8 %13
+               OpBranch %28
+         %28 = OpLabel
+         %29 = OpPhi %8 %10 %17
+               OpBranch %15
+         %15 = OpLabel
+         %30 = OpPhi %8 %25 %19 %29 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp b/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp
new file mode 100644
index 0000000..1712855
--- /dev/null
+++ b/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp
@@ -0,0 +1,276 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Inapplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 1
+          %7 = OpConstant %5 2
+          %8 = OpTypeVector %5 4
+          %9 = OpConstantNull %8
+         %10 = OpConstantComposite %8 %6 %6 %7 %7
+         %11 = OpTypeBool
+         %12 = OpTypeVector %11 4
+         %13 = OpConstantTrue %11
+         %14 = OpConstantFalse %11
+         %15 = OpConstantComposite %12 %13 %14 %14 %13
+          %2 = OpFunction %3 None %4
+         %16 = OpLabel
+         %17 = OpCopyObject %5 %6
+         %18 = OpCopyObject %5 %7
+               OpBranch %19
+         %19 = OpLabel
+         %20 = OpCopyObject %5 %17
+         %21 = OpSelect %5 %13 %17 %18
+               OpBranch %22
+         %22 = OpLabel
+         %23 = OpSelect %8 %15 %9 %10
+               OpBranch %24
+         %24 = OpLabel
+               OpSelectionMerge %25 None
+               OpBranchConditional %13 %26 %27
+         %26 = OpLabel
+         %28 = OpSelect %5 %13 %17 %18
+               OpBranch %27
+         %27 = OpLabel
+         %29 = OpSelect %5 %13 %17 %18
+               OpBranch %25
+         %25 = OpLabel
+         %30 = OpSelect %5 %13 %17 %18
+               OpBranch %31
+         %31 = OpLabel
+               OpLoopMerge %32 %33 None
+               OpBranch %33
+         %33 = OpLabel
+         %34 = OpSelect %5 %13 %17 %18
+               OpBranchConditional %13 %31 %32
+         %32 = OpLabel
+         %35 = OpSelect %5 %13 %17 %18
+               OpBranch %36
+         %36 = OpLabel
+         %37 = OpSelect %5 %13 %17 %18
+               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);
+  // %20 is not an OpSelect instruction.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %21 is not the first instruction in its block.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(21, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The condition for %23 is not a scalar, but a vector of booleans.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(23, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The predecessor (%24) of the block containing %28 is the header of a
+  // selection construct and does not branch unconditionally.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(24, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The block containing %29 has two predecessors (%24 and %26).
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(29, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The block containing %30 is the merge block for a selection construct.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(30, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The predecessor (%31) of the block containing %34 is a loop header.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(31, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // The block containing %35 is the merge block for a loop construct.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(35, 100, 101)
+                   .IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  // |true_block_id| and |false_block_id| are both 0.
+  ASSERT_DEATH(
+      TransformationReplaceOpSelectWithConditionalBranch(37, 0, 0).IsApplicable(
+          context.get(), transformation_context),
+      "At least one of the ids must be non-zero.");
+#endif
+
+  // The fresh ids are not distinct.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 100)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // One of the ids is not fresh.
+  ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 10)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Simple) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 1
+          %7 = OpConstant %5 2
+          %8 = OpTypeVector %5 4
+          %9 = OpConstantNull %8
+         %10 = OpConstantComposite %8 %6 %6 %7 %7
+         %11 = OpTypeBool
+         %12 = OpTypeVector %11 4
+         %13 = OpConstantTrue %11
+         %14 = OpConstantFalse %11
+         %15 = OpConstantComposite %12 %13 %14 %14 %13
+          %2 = OpFunction %3 None %4
+         %16 = OpLabel
+         %17 = OpCopyObject %5 %6
+         %18 = OpCopyObject %5 %7
+               OpBranch %19
+         %19 = OpLabel
+         %20 = OpSelect %5 %13 %17 %18
+               OpSelectionMerge %21 None
+               OpBranchConditional %13 %22 %21
+         %22 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+         %24 = OpSelect %8 %13 %9 %10
+               OpBranch %21
+         %21 = OpLabel
+               OpBranch %25
+         %25 = OpLabel
+         %26 = OpSelect %5 %13 %17 %18
+               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);
+  auto transformation =
+      TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+
+  auto transformation2 =
+      TransformationReplaceOpSelectWithConditionalBranch(24, 0, 102);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+
+  auto transformation3 =
+      TransformationReplaceOpSelectWithConditionalBranch(26, 103, 0);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, 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 %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeInt 32 1
+          %6 = OpConstant %5 1
+          %7 = OpConstant %5 2
+          %8 = OpTypeVector %5 4
+          %9 = OpConstantNull %8
+         %10 = OpConstantComposite %8 %6 %6 %7 %7
+         %11 = OpTypeBool
+         %12 = OpTypeVector %11 4
+         %13 = OpConstantTrue %11
+         %14 = OpConstantFalse %11
+         %15 = OpConstantComposite %12 %13 %14 %14 %13
+          %2 = OpFunction %3 None %4
+         %16 = OpLabel
+         %17 = OpCopyObject %5 %6
+         %18 = OpCopyObject %5 %7
+               OpSelectionMerge %19 None
+               OpBranchConditional %13 %100 %101
+        %100 = OpLabel
+               OpBranch %19
+        %101 = OpLabel
+               OpBranch %19
+         %19 = OpLabel
+         %20 = OpPhi %5 %17 %100 %18 %101
+               OpSelectionMerge %21 None
+               OpBranchConditional %13 %22 %21
+         %22 = OpLabel
+               OpSelectionMerge %23 None
+               OpBranchConditional %13 %23 %102
+        %102 = OpLabel
+               OpBranch %23
+         %23 = OpLabel
+         %24 = OpPhi %8 %9 %22 %10 %102
+               OpBranch %21
+         %21 = OpLabel
+               OpSelectionMerge %25 None
+               OpBranchConditional %13 %103 %25
+        %103 = OpLabel
+               OpBranch %25
+         %25 = OpLabel
+         %26 = OpPhi %5 %17 %103 %18 %21
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_replace_parameter_with_global_test.cpp b/test/fuzz/transformation_replace_parameter_with_global_test.cpp
index 25e25db..478d809 100644
--- a/test/fuzz/transformation_replace_parameter_with_global_test.cpp
+++ b/test/fuzz/transformation_replace_parameter_with_global_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_replace_parameter_with_global.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -113,13 +116,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Parameter id is invalid.
   ASSERT_FALSE(TransformationReplaceParameterWithGlobal(50, 50, 51)
                    .IsApplicable(context.get(), transformation_context));
@@ -156,46 +157,54 @@
     TransformationReplaceParameterWithGlobal transformation(50, 16, 51);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParameterWithGlobal transformation(52, 17, 53);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParameterWithGlobal transformation(54, 18, 55);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParameterWithGlobal transformation(56, 19, 57);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParameterWithGlobal transformation(58, 75, 59);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParameterWithGlobal transformation(60, 81, 61);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParameterWithGlobal transformation(62, 91, 63);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -323,28 +332,30 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  fact_manager.AddFactIdIsIrrelevant(10);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(10);
 
   {
     TransformationReplaceParameterWithGlobal transformation(20, 10, 21);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(21));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(21));
   }
   {
     TransformationReplaceParameterWithGlobal transformation(22, 11, 23);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_FALSE(fact_manager.PointeeValueIsIrrelevant(23));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_FALSE(
+        transformation_context.GetFactManager()->PointeeValueIsIrrelevant(23));
   }
 }
 
diff --git a/test/fuzz/transformation_replace_params_with_struct_test.cpp b/test/fuzz/transformation_replace_params_with_struct_test.cpp
index e59f6ea..afa782e 100644
--- a/test/fuzz/transformation_replace_params_with_struct_test.cpp
+++ b/test/fuzz/transformation_replace_params_with_struct_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_replace_params_with_struct.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -122,13 +124,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // |parameter_id| is empty.
   ASSERT_FALSE(
       TransformationReplaceParamsWithStruct({}, 90, 91, {{33, 92}, {90, 93}})
@@ -163,9 +163,9 @@
                    .IsApplicable(context.get(), transformation_context));
 
   // |caller_id_to_fresh_composite_id| misses values.
-  ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 91,
-                                                     {{33, 92}, {90, 93}})
-                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(
+      TransformationReplaceParamsWithStruct({16, 17}, 90, 91, {{90, 93}})
+          .IsApplicable(context.get(), transformation_context));
 
   // All fresh ids must be unique.
   ASSERT_FALSE(TransformationReplaceParamsWithStruct({16, 17}, 90, 90,
@@ -194,42 +194,49 @@
                                                          {{33, 92}, {90, 93}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParamsWithStruct transformation({43}, 93, 94,
                                                          {{33, 95}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParamsWithStruct transformation({17, 91, 94}, 96, 97,
                                                          {{33, 98}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParamsWithStruct transformation({55}, 99, 100, {{}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParamsWithStruct transformation({61}, 101, 102, {{}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
   {
     TransformationReplaceParamsWithStruct transformation({73}, 103, 104, {{}});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string expected_shader = R"(
                OpCapability Shader
@@ -333,6 +340,188 @@
   ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
 }
 
+TEST(TransformationReplaceParamsWithStructTest, ParametersRemainValid) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %6 = OpTypeInt 32 1
+          %3 = OpTypeFunction %2
+          %8 = OpTypeFloat 32
+         %10 = OpTypeVector %8 2
+         %12 = OpTypeBool
+         %40 = OpTypePointer Function %12
+         %13 = OpTypeStruct %6 %8
+         %45 = OpTypeStruct %6 %8 %13
+         %47 = OpTypeStruct %45 %12 %10
+         %15 = OpTypeFunction %2 %6 %8 %10 %13 %40 %12
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %2 None %15
+         %16 = OpFunctionParameter %6
+         %17 = OpFunctionParameter %8
+         %18 = OpFunctionParameter %10
+         %19 = OpFunctionParameter %13
+         %42 = OpFunctionParameter %40
+         %43 = OpFunctionParameter %12
+         %21 = 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);
+  {
+    // Try to replace parameters in "increasing" order of their declaration.
+    TransformationReplaceParamsWithStruct transformation({16, 17, 19}, 70, 71,
+                                                         {{}});
+    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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %6 = OpTypeInt 32 1
+          %3 = OpTypeFunction %2
+          %8 = OpTypeFloat 32
+         %10 = OpTypeVector %8 2
+         %12 = OpTypeBool
+         %40 = OpTypePointer Function %12
+         %13 = OpTypeStruct %6 %8
+         %45 = OpTypeStruct %6 %8 %13
+         %47 = OpTypeStruct %45 %12 %10
+         %15 = OpTypeFunction %2 %10 %40 %12 %45
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %2 None %15
+         %18 = OpFunctionParameter %10
+         %42 = OpFunctionParameter %40
+         %43 = OpFunctionParameter %12
+         %71 = OpFunctionParameter %45
+         %21 = OpLabel
+         %19 = OpCompositeExtract %13 %71 2
+         %17 = OpCompositeExtract %8 %71 1
+         %16 = OpCompositeExtract %6 %71 0
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+
+  {
+    // Try to replace parameters in "decreasing" order of their declaration.
+    TransformationReplaceParamsWithStruct transformation({71, 43, 18}, 72, 73,
+                                                         {{}});
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+
+  after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %6 = OpTypeInt 32 1
+          %3 = OpTypeFunction %2
+          %8 = OpTypeFloat 32
+         %10 = OpTypeVector %8 2
+         %12 = OpTypeBool
+         %40 = OpTypePointer Function %12
+         %13 = OpTypeStruct %6 %8
+         %45 = OpTypeStruct %6 %8 %13
+         %47 = OpTypeStruct %45 %12 %10
+         %15 = OpTypeFunction %2 %40 %47
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %20 = OpFunction %2 None %15
+         %42 = OpFunctionParameter %40
+         %73 = OpFunctionParameter %47
+         %21 = OpLabel
+         %18 = OpCompositeExtract %10 %73 2
+         %43 = OpCompositeExtract %12 %73 1
+         %71 = OpCompositeExtract %45 %73 0
+         %19 = OpCompositeExtract %13 %71 2
+         %17 = OpCompositeExtract %8 %71 1
+         %16 = OpCompositeExtract %6 %71 0
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationReplaceParamsWithStructTest, IsomorphicStructs) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %16 "main"
+               OpExecutionMode %16 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %6 = OpTypeInt 32 1
+          %7 = OpTypeStruct %6
+          %8 = OpTypeStruct %6
+          %9 = OpTypeStruct %8
+         %10 = OpTypeFunction %2 %7
+         %15 = OpTypeFunction %2
+         %16 = OpFunction %2 None %15
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %11 = OpFunction %2 None %10
+         %12 = OpFunctionParameter %7
+         %13 = 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);
+
+  ASSERT_FALSE(TransformationReplaceParamsWithStruct({12}, 100, 101, {{}})
+                   .IsApplicable(context.get(), transformation_context));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/test/fuzz/transformation_set_function_control_test.cpp b/test/fuzz/transformation_set_function_control_test.cpp
index be7f2be..85402e1 100644
--- a/test/fuzz/transformation_set_function_control_test.cpp
+++ b/test/fuzz/transformation_set_function_control_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_set_function_control.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -117,11 +120,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // %36 is not a function
   ASSERT_FALSE(TransformationSetFunctionControl(36, SpvFunctionControlMaskNone)
                    .IsApplicable(context.get(), transformation_context));
@@ -138,28 +139,32 @@
                                                    SpvFunctionControlMaskNone);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
 
   // Set to Inline; silly to do it on an entry point, but it is allowed
   TransformationSetFunctionControl transformation2(
       4, SpvFunctionControlInlineMask);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
 
   // Set to Pure, removing DontInline
   TransformationSetFunctionControl transformation3(17,
                                                    SpvFunctionControlPureMask);
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
 
   // Change from Inline to DontInline
   TransformationSetFunctionControl transformation4(
       13, SpvFunctionControlDontInlineMask);
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_set_loop_control_test.cpp b/test/fuzz/transformation_set_loop_control_test.cpp
index 531aa7a..3312a67 100644
--- a/test/fuzz/transformation_set_loop_control_test.cpp
+++ b/test/fuzz/transformation_set_loop_control_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_set_loop_control.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -253,13 +256,11 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // These are the loop headers together with the selection controls of their
   // merge instructions:
   //  %10 None
@@ -612,49 +613,63 @@
           7, 9)
           .IsApplicable(context.get(), transformation_context));
 
-  TransformationSetLoopControl(10,
-                               SpvLoopControlUnrollMask |
-                                   SpvLoopControlPeelCountMask |
-                                   SpvLoopControlPartialCountMask,
-                               3, 3)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(
-      43, SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
-      0, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(63,
-                               SpvLoopControlUnrollMask |
-                                   SpvLoopControlMinIterationsMask |
-                                   SpvLoopControlPeelCountMask,
-                               23, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(73,
-                               SpvLoopControlUnrollMask |
-                                   SpvLoopControlMaxIterationsMask |
-                                   SpvLoopControlPeelCountMask,
-                               23, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(
-      93, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 16, 8)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0)
-      .Apply(context.get(), &transformation_context);
-  TransformationSetLoopControl(
-      123,
-      SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
-          SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
-      0, 9)
-      .Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(10,
+                                   SpvLoopControlUnrollMask |
+                                       SpvLoopControlPeelCountMask |
+                                       SpvLoopControlPartialCountMask,
+                                   3, 3),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(23, SpvLoopControlDontUnrollMask, 0, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(33, SpvLoopControlUnrollMask, 0, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(
+          43,
+          SpvLoopControlDontUnrollMask | SpvLoopControlDependencyInfiniteMask,
+          0, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(53, SpvLoopControlMaskNone, 0, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(63,
+                                   SpvLoopControlUnrollMask |
+                                       SpvLoopControlMinIterationsMask |
+                                       SpvLoopControlPeelCountMask,
+                                   23, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(73,
+                                   SpvLoopControlUnrollMask |
+                                       SpvLoopControlMaxIterationsMask |
+                                       SpvLoopControlPeelCountMask,
+                                   23, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(83, SpvLoopControlDontUnrollMask, 0, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(
+          93, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 16,
+          8),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(103, SpvLoopControlPartialCountMask, 0, 60),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(113, SpvLoopControlPeelCountMask, 12, 0),
+      context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(
+      TransformationSetLoopControl(
+          123,
+          SpvLoopControlUnrollMask | SpvLoopControlMinIterationsMask |
+              SpvLoopControlMaxIterationsMask | SpvLoopControlPartialCountMask,
+          0, 9),
+      context.get(), &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
@@ -930,43 +945,40 @@
                OpFunctionEnd
   )";
 
-  const auto consumer = nullptr;
-  const auto context_1_0 =
-      BuildModule(SPV_ENV_UNIVERSAL_1_0, consumer, shader, kFuzzAssembleOption);
-  const auto context_1_1 =
-      BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, shader, kFuzzAssembleOption);
-  const auto context_1_2 =
-      BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer, shader, kFuzzAssembleOption);
-  const auto context_1_3 =
-      BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer, shader, kFuzzAssembleOption);
-  const auto context_1_4 =
-      BuildModule(SPV_ENV_UNIVERSAL_1_4, consumer, shader, kFuzzAssembleOption);
-  const auto context_1_5 =
-      BuildModule(SPV_ENV_UNIVERSAL_1_5, consumer, shader, kFuzzAssembleOption);
+  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}) {
+    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);
+    TransformationSetLoopControl transformation(
+        10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4);
 
-  FactManager fact_manager;
-  spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  TransformationSetLoopControl set_peel_and_partial(
-      10, SpvLoopControlPeelCountMask | SpvLoopControlPartialCountMask, 4, 4);
-
-  // PeelCount and PartialCount were introduced in SPIRV 1.4, so are not valid
-  // in the context of older versions.
-  ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_0.get(),
-                                                 transformation_context));
-  ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_1.get(),
-                                                 transformation_context));
-  ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_2.get(),
-                                                 transformation_context));
-  ASSERT_FALSE(set_peel_and_partial.IsApplicable(context_1_3.get(),
-                                                 transformation_context));
-
-  ASSERT_TRUE(set_peel_and_partial.IsApplicable(context_1_4.get(),
-                                                transformation_context));
-  ASSERT_TRUE(set_peel_and_partial.IsApplicable(context_1_5.get(),
-                                                transformation_context));
+    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:
+        // 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));
+        break;
+      case SPV_ENV_UNIVERSAL_1_4:
+      case SPV_ENV_UNIVERSAL_1_5:
+        ASSERT_TRUE(
+            transformation.IsApplicable(context.get(), transformation_context));
+        break;
+      default:
+        assert(false && "Unhandled environment");
+        break;
+    }
+  }
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
index 518ce9d..4763d7a 100644
--- a/test/fuzz/transformation_set_memory_operands_mask_test.cpp
+++ b/test/fuzz/transformation_set_memory_operands_mask_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_set_memory_operands_mask.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -90,13 +93,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               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),
@@ -116,7 +117,8 @@
         SpvMemoryAccessAlignedMask | SpvMemoryAccessVolatileMask, 0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   // Not OK to remove Aligned
@@ -140,7 +142,8 @@
         0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   // Not OK to remove Volatile
@@ -162,7 +165,8 @@
         SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -172,7 +176,8 @@
         SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -182,7 +187,8 @@
         SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -192,7 +198,8 @@
         SpvMemoryAccessVolatileMask, 0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   std::string after_transformation = R"(
@@ -335,13 +342,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               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),
@@ -353,7 +358,8 @@
                      .IsApplicable(context.get(), transformation_context));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -367,7 +373,8 @@
                      .IsApplicable(context.get(), transformation_context));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -377,7 +384,8 @@
         SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -387,7 +395,8 @@
         SpvMemoryAccessNontemporalMask | SpvMemoryAccessVolatileMask, 1);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -402,7 +411,8 @@
             .IsApplicable(context.get(), transformation_context));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -411,7 +421,8 @@
         SpvMemoryAccessVolatileMask, 1);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -420,7 +431,8 @@
         SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask, 0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   {
@@ -429,7 +441,8 @@
         0);
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
   }
 
   std::string after_transformation = R"(
diff --git a/test/fuzz/transformation_set_selection_control_test.cpp b/test/fuzz/transformation_set_selection_control_test.cpp
index 9afb89d..c584ff1 100644
--- a/test/fuzz/transformation_set_selection_control_test.cpp
+++ b/test/fuzz/transformation_set_selection_control_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_set_selection_control.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -102,11 +105,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // %44 is not a block
   ASSERT_FALSE(
       TransformationSetSelectionControl(44, SpvSelectionControlFlattenMask)
@@ -124,25 +125,29 @@
       11, SpvSelectionControlDontFlattenMask);
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
 
   TransformationSetSelectionControl transformation2(
       23, SpvSelectionControlFlattenMask);
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
 
   TransformationSetSelectionControl transformation3(
       31, SpvSelectionControlMaskNone);
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
 
   TransformationSetSelectionControl transformation4(
       31, SpvSelectionControlFlattenMask);
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_split_block_test.cpp b/test/fuzz/transformation_split_block_test.cpp
index 30bac02..55091de 100644
--- a/test/fuzz/transformation_split_block_test.cpp
+++ b/test/fuzz/transformation_split_block_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_split_block.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -88,11 +91,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // No split before OpVariable
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(8, SpvOpVariable, 0), 100)
@@ -201,16 +202,15 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto split_1 = TransformationSplitBlock(
       MakeInstructionDescriptor(5, SpvOpStore, 0), 100);
   ASSERT_TRUE(split_1.IsApplicable(context.get(), transformation_context));
-  split_1.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(split_1, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_split_1 = R"(
                OpCapability Shader
@@ -257,8 +257,9 @@
   auto split_2 = TransformationSplitBlock(
       MakeInstructionDescriptor(11, SpvOpStore, 0), 101);
   ASSERT_TRUE(split_2.IsApplicable(context.get(), transformation_context));
-  split_2.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(split_2, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_split_2 = R"(
                OpCapability Shader
@@ -307,8 +308,9 @@
   auto split_3 = TransformationSplitBlock(
       MakeInstructionDescriptor(14, SpvOpLoad, 0), 102);
   ASSERT_TRUE(split_3.IsApplicable(context.get(), transformation_context));
-  split_3.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(split_3, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_split_3 = R"(
                OpCapability Shader
@@ -417,11 +419,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Illegal to split between the merge and the conditional branch.
   ASSERT_FALSE(
       TransformationSplitBlock(
@@ -435,8 +435,9 @@
   auto split = TransformationSplitBlock(
       MakeInstructionDescriptor(14, SpvOpSelectionMerge, 0), 100);
   ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
-  split.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(split, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_split = R"(
                OpCapability Shader
@@ -549,11 +550,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Illegal to split between the merge and the conditional branch.
   ASSERT_FALSE(TransformationSplitBlock(
                    MakeInstructionDescriptor(9, SpvOpSwitch, 0), 100)
@@ -565,8 +564,9 @@
   auto split = TransformationSplitBlock(
       MakeInstructionDescriptor(9, SpvOpSelectionMerge, 0), 100);
   ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
-  split.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(split, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_split = R"(
                OpCapability Shader
@@ -685,11 +685,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // We cannot split before OpPhi instructions, since the number of incoming
   // blocks may not appropriately match after splitting.
   ASSERT_FALSE(
@@ -740,11 +738,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   ASSERT_TRUE(
       TransformationSplitBlock(MakeInstructionDescriptor(21, SpvOpPhi, 0), 100)
           .IsApplicable(context.get(), transformation_context));
@@ -753,8 +749,9 @@
   auto split =
       TransformationSplitBlock(MakeInstructionDescriptor(20, SpvOpPhi, 0), 100);
   ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
-  split.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(split, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   std::string after_split = R"(
                OpCapability Shader
@@ -822,19 +819,18 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Record the fact that block 8 is dead.
   transformation_context.GetFactManager()->AddFactBlockIsDead(8);
 
   auto split = TransformationSplitBlock(
       MakeInstructionDescriptor(8, SpvOpBranch, 0), 100);
   ASSERT_TRUE(split.IsApplicable(context.get(), transformation_context));
-  split.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  ApplyAndCheckFreshIds(split, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
 
   ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(8));
   ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(100));
@@ -912,11 +908,9 @@
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
 
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto split = TransformationSplitBlock(
       MakeInstructionDescriptor(217, SpvOpImageSampleImplicitLod, 0), 500);
   ASSERT_FALSE(split.IsApplicable(context.get(), transformation_context));
diff --git a/test/fuzz/transformation_store_test.cpp b/test/fuzz/transformation_store_test.cpp
index 07d222f..93257d0 100644
--- a/test/fuzz/transformation_store_test.cpp
+++ b/test/fuzz/transformation_store_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_store.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -91,13 +94,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
       27);
   transformation_context.GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
@@ -233,8 +234,10 @@
         27, 80, MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -243,8 +246,10 @@
         11, 95, MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -253,8 +258,10 @@
         46, 80, MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -263,8 +270,10 @@
         16, 21, MakeInstructionDescriptor(95, SpvOpReturnValue, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   {
@@ -273,8 +282,10 @@
         53, 21, MakeInstructionDescriptor(38, SpvOpAccessChain, 0));
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
-    transformation.Apply(context.get(), &transformation_context);
-    ASSERT_TRUE(IsValid(env, context.get()));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
   }
 
   std::string after_transformation = R"(
@@ -395,14 +406,12 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  fact_manager.AddFactBlockIsDead(5);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(5);
 
   ASSERT_FALSE(
       TransformationStore(15, 13, MakeInstructionDescriptor(27, SpvOpReturn, 0))
diff --git a/test/fuzz/transformation_swap_commutable_operands_test.cpp b/test/fuzz/transformation_swap_commutable_operands_test.cpp
index c213dfe..0731529 100644
--- a/test/fuzz/transformation_swap_commutable_operands_test.cpp
+++ b/test/fuzz/transformation_swap_commutable_operands_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_swap_commutable_operands.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -109,13 +112,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests existing commutative instructions
   auto instructionDescriptor = MakeInstructionDescriptor(22, SpvOpIAdd, 0);
   auto transformation =
@@ -336,33 +337,31 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instructionDescriptor = MakeInstructionDescriptor(22, SpvOpIAdd, 0);
   auto transformation =
       TransformationSwapCommutableOperands(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor = MakeInstructionDescriptor(28, SpvOpIMul, 0);
   transformation = TransformationSwapCommutableOperands(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor = MakeInstructionDescriptor(42, SpvOpFAdd, 0);
   transformation = TransformationSwapCommutableOperands(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor = MakeInstructionDescriptor(48, SpvOpFMul, 0);
   transformation = TransformationSwapCommutableOperands(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor = MakeInstructionDescriptor(66, SpvOpDot, 0);
   transformation = TransformationSwapCommutableOperands(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variantShader = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp b/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp
index 4383e07..e7a8732 100644
--- a/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp
+++ b/test/fuzz/transformation_swap_conditional_branch_operands_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_swap_conditional_branch_operands.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -66,13 +69,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Invalid instruction descriptor.
   ASSERT_FALSE(TransformationSwapConditionalBranchOperands(
                    MakeInstructionDescriptor(26, SpvOpPhi, 0), 26)
@@ -92,7 +93,7 @@
       MakeInstructionDescriptor(15, SpvOpBranchConditional, 0), 26);
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string after_transformation = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp b/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp
index b20f59e..84ed20d 100644
--- a/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp
+++ b/test/fuzz/transformation_toggle_access_chain_instruction_test.cpp
@@ -13,6 +13,9 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_toggle_access_chain_instruction.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -109,13 +112,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Tests existing access chain instructions
   auto instructionDescriptor =
       MakeInstructionDescriptor(18, SpvOpAccessChain, 0);
@@ -305,40 +306,38 @@
   const auto env = SPV_ENV_UNIVERSAL_1_5;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   auto instructionDescriptor =
       MakeInstructionDescriptor(18, SpvOpAccessChain, 0);
   auto transformation =
       TransformationToggleAccessChainInstruction(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor =
       MakeInstructionDescriptor(20, SpvOpInBoundsAccessChain, 0);
   transformation =
       TransformationToggleAccessChainInstruction(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor = MakeInstructionDescriptor(24, SpvOpAccessChain, 0);
   transformation =
       TransformationToggleAccessChainInstruction(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor =
       MakeInstructionDescriptor(26, SpvOpInBoundsAccessChain, 0);
   transformation =
       TransformationToggleAccessChainInstruction(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   instructionDescriptor = MakeInstructionDescriptor(38, SpvOpAccessChain, 0);
   transformation =
       TransformationToggleAccessChainInstruction(instructionDescriptor);
-  transformation.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
 
   std::string variantShader = R"(
                OpCapability Shader
diff --git a/test/fuzz/transformation_vector_shuffle_test.cpp b/test/fuzz/transformation_vector_shuffle_test.cpp
index f72fc95..e3dc0a7 100644
--- a/test/fuzz/transformation_vector_shuffle_test.cpp
+++ b/test/fuzz/transformation_vector_shuffle_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/fuzz/transformation_vector_shuffle.h"
 
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "test/fuzz/fuzz_test_util.h"
 
@@ -21,7 +23,7 @@
 namespace fuzz {
 namespace {
 
-TEST(TransformationVectorShuffle, BasicTest) {
+TEST(TransformationVectorShuffleTest, BasicTest) {
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -84,91 +86,89 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               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(10, {}), MakeDataDescriptor(12, {0}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(12, {1}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0}), context.get());
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(11, {}), MakeDataDescriptor(12, {1}), context.get());
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(16, {1}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {2}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {0}), context.get());
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(11, {}), MakeDataDescriptor(16, {1}), context.get());
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {1}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(10, {}), MakeDataDescriptor(16, {2}), context.get());
+      MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {2}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {3}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {0}), context.get());
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(27, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {1}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(10, {}), MakeDataDescriptor(20, {2}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(11, {}), MakeDataDescriptor(20, {3}), context.get());
+      MakeDataDescriptor(26, {}), MakeDataDescriptor(27, {1}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(25, {}), MakeDataDescriptor(27, {0}), context.get());
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(26, {}), MakeDataDescriptor(27, {1}), context.get());
+      MakeDataDescriptor(26, {}), MakeDataDescriptor(31, {1}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {2}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {0}), context.get());
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(26, {}), MakeDataDescriptor(31, {1}), context.get());
+      MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {1}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(25, {}), MakeDataDescriptor(31, {2}), context.get());
+      MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {2}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {3}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {0}), context.get());
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(42, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {1}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(25, {}), MakeDataDescriptor(35, {2}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(26, {}), MakeDataDescriptor(35, {3}), context.get());
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(42, {1}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(40, {}), MakeDataDescriptor(42, {0}), context.get());
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(41, {}), MakeDataDescriptor(42, {1}), context.get());
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(46, {1}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {2}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {0}), context.get());
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(41, {}), MakeDataDescriptor(46, {1}), context.get());
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {1}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(40, {}), MakeDataDescriptor(46, {2}), context.get());
+      MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {2}));
+  transformation_context.GetFactManager()->AddFactDataSynonym(
+      MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {3}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {0}), context.get());
+      MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {1}), context.get());
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(61, {1}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(40, {}), MakeDataDescriptor(50, {2}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(41, {}), MakeDataDescriptor(50, {3}), context.get());
+      MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {2}));
 
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {0}), context.get());
+      MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {0}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(56, {}), MakeDataDescriptor(61, {1}), context.get());
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {1}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(55, {}), MakeDataDescriptor(61, {2}), context.get());
-
+      MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {2}));
   transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {0}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {1}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(55, {}), MakeDataDescriptor(65, {2}), context.get());
-  transformation_context.GetFactManager()->AddFactDataSynonym(
-      MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {3}), context.get());
+      MakeDataDescriptor(56, {}), MakeDataDescriptor(65, {3}));
 
   // %103 does not dominate the return instruction.
   ASSERT_FALSE(TransformationVectorShuffle(
@@ -248,7 +248,8 @@
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {1, 0});
   ASSERT_TRUE(
       transformation1.IsApplicable(context.get(), transformation_context));
-  transformation1.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
   temp_dd = MakeDataDescriptor(200, {0});
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(11, {}), temp_dd));
@@ -261,7 +262,8 @@
       {0xFFFFFFFF, 3, 5});
   ASSERT_TRUE(
       transformation2.IsApplicable(context.get(), transformation_context));
-  transformation2.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
   temp_dd = MakeDataDescriptor(201, {1});
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(11, {}), temp_dd));
@@ -273,7 +275,8 @@
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 202, 27, 35, {5, 4, 1});
   ASSERT_TRUE(
       transformation3.IsApplicable(context.get(), transformation_context));
-  transformation3.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
   temp_dd = MakeDataDescriptor(202, {0});
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(26, {}), temp_dd));
@@ -288,7 +291,8 @@
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 203, 42, 46, {0, 1});
   ASSERT_TRUE(
       transformation4.IsApplicable(context.get(), transformation_context));
-  transformation4.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
   temp_dd = MakeDataDescriptor(203, {0});
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(40, {}), temp_dd));
@@ -300,7 +304,8 @@
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 204, 42, 46, {2, 3, 4});
   ASSERT_TRUE(
       transformation5.IsApplicable(context.get(), transformation_context));
-  transformation5.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation5, context.get(),
+                        &transformation_context);
   temp_dd = MakeDataDescriptor(204, {0});
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(40, {}), temp_dd));
@@ -316,7 +321,8 @@
       {0, 1, 2, 3});
   ASSERT_TRUE(
       transformation6.IsApplicable(context.get(), transformation_context));
-  transformation6.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation6, context.get(),
+                        &transformation_context);
   temp_dd = MakeDataDescriptor(205, {0});
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(40, {}), temp_dd));
@@ -336,7 +342,8 @@
       {0xFFFFFFFF, 3, 6, 0xFFFFFFFF});
   ASSERT_TRUE(
       transformation7.IsApplicable(context.get(), transformation_context));
-  transformation7.Apply(context.get(), &transformation_context);
+  ApplyAndCheckFreshIds(transformation7, context.get(),
+                        &transformation_context);
   temp_dd = MakeDataDescriptor(206, {1});
   ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
       MakeDataDescriptor(56, {}), temp_dd));
@@ -487,13 +494,11 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   // Cannot insert before the OpVariables of a function.
   ASSERT_FALSE(
       TransformationVectorShuffle(
@@ -541,7 +546,7 @@
                    .IsApplicable(context.get(), transformation_context));
 }
 
-TEST(TransformationVectorShuffle, HandlesIrrelevantIds1) {
+TEST(TransformationVectorShuffleTest, HandlesIrrelevantIds1) {
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -604,26 +609,25 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
   TransformationVectorShuffle transformation(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {2, 0});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {0}),
-                                        MakeDataDescriptor(200, {1})));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(112, {0}),
-                                        MakeDataDescriptor(200, {0})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {0}), MakeDataDescriptor(200, {1})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(112, {0}), MakeDataDescriptor(200, {0})));
 }
 
-TEST(TransformationVectorShuffle, HandlesIrrelevantIds2) {
+TEST(TransformationVectorShuffleTest, HandlesIrrelevantIds2) {
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -686,24 +690,115 @@
   const auto env = SPV_ENV_UNIVERSAL_1_4;
   const auto consumer = nullptr;
   const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  FactManager fact_manager;
   spvtools::ValidatorOptions validator_options;
-  TransformationContext transformation_context(&fact_manager,
-                                               validator_options);
-
-  fact_manager.AddFactIdIsIrrelevant(112);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(112);
   TransformationVectorShuffle transformation(
       MakeInstructionDescriptor(100, SpvOpReturn, 0), 200, 12, 112, {2, 0});
   ASSERT_TRUE(
       transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
-  ASSERT_TRUE(fact_manager.IsSynonymous(MakeDataDescriptor(12, {0}),
-                                        MakeDataDescriptor(200, {1})));
-  ASSERT_FALSE(fact_manager.IsSynonymous(MakeDataDescriptor(112, {0}),
-                                         MakeDataDescriptor(200, {0})));
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  // Because %12 is not irrelevant, we get a synonym between it and %200[1].
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(12, {0}), MakeDataDescriptor(200, {1})));
+  // Because %112 is irrelevant, we do not get a synonym between it and %200[0].
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(112, {0}), MakeDataDescriptor(200, {0})));
+}
+
+TEST(TransformationVectorShuffleTest, HandlesIrrelevantIds3) {
+  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 0
+         %11 = OpConstant %6 1
+         %12 = OpConstantComposite %7 %10 %11
+         %40 = OpConstantComposite %7 %10 %11
+         %13 = OpTypeBool
+         %14 = OpConstantFalse %13
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %9 = OpVariable %8 Function
+               OpStore %9 %12
+               OpSelectionMerge %16 None
+               OpBranchConditional %14 %15 %16
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+               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);
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(40);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(15);
+
+  TransformationVectorShuffle transformation1(
+      MakeInstructionDescriptor(15, SpvOpBranch, 0), 200, 12, 12, {0, 3});
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation1, context.get(),
+                        &transformation_context);
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(200, {0}), MakeDataDescriptor(12, {0})));
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(200, {1}), MakeDataDescriptor(12, {1})));
+
+  TransformationVectorShuffle transformation2(
+      MakeInstructionDescriptor(16, SpvOpReturn, 0), 201, 12, 40, {0, 1});
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation2, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(201, {0}), MakeDataDescriptor(12, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(201, {1}), MakeDataDescriptor(12, {1})));
+
+  TransformationVectorShuffle transformation3(
+      MakeInstructionDescriptor(16, SpvOpReturn, 0), 202, 40, 12, {2, 3});
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation3, context.get(),
+                        &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(202, {0}), MakeDataDescriptor(12, {0})));
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(202, {1}), MakeDataDescriptor(12, {1})));
+
+  TransformationVectorShuffle transformation4(
+      MakeInstructionDescriptor(16, SpvOpReturn, 0), 203, 40, 12, {0, 3});
+  ASSERT_TRUE(
+      transformation4.IsApplicable(context.get(), transformation_context));
+  ApplyAndCheckFreshIds(transformation4, context.get(),
+                        &transformation_context);
+  // Because %40 is irrelevant we do not get a synonym between it and %203[0].
+  ASSERT_FALSE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(203, {0}), MakeDataDescriptor(40, {0})));
+  // Because %12 is *not* irrelevant we do get a synonym between it and %203[1].
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(203, {1}), MakeDataDescriptor(12, {1})));
 }
 
 }  // namespace
diff --git a/test/fuzz/transformation_wrap_early_terminator_in_function_test.cpp b/test/fuzz/transformation_wrap_early_terminator_in_function_test.cpp
new file mode 100644
index 0000000..7b4e487
--- /dev/null
+++ b/test/fuzz/transformation_wrap_early_terminator_in_function_test.cpp
@@ -0,0 +1,318 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_early_terminator_in_function.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(TransformationWrapEarlyTerminatorInFunctionTest, IsApplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_terminate_invocation"
+          %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
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 0
+         %90 = OpTypeBool
+         %91 = OpConstantFalse %90
+
+         %20 = OpTypeFunction %2 %6
+         %21 = OpTypeFunction %6
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %11 None
+               OpSwitch %7 %11 0 %8 1 %9 2 %10
+          %8 = OpLabel
+               OpKill
+          %9 = OpLabel
+               OpUnreachable
+         %10 = OpLabel
+               OpTerminateInvocation
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         %30 = OpFunction %2 None %3
+         %31 = OpLabel
+               OpKill
+               OpFunctionEnd
+
+         %50 = OpFunction %2 None %3
+         %51 = OpLabel
+               OpTerminateInvocation
+               OpFunctionEnd
+
+         %60 = OpFunction %6 None %21
+         %61 = OpLabel
+               OpBranch %62
+         %62 = OpLabel
+               OpKill
+               OpFunctionEnd
+
+         %70 = OpFunction %6 None %21
+         %71 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+
+         %80 = OpFunction %2 None %20
+         %81 = OpFunctionParameter %6
+         %82 = OpLabel
+               OpTerminateInvocation
+               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: id is not fresh
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   61, MakeInstructionDescriptor(8, SpvOpKill, 0), 0)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: early terminator instruction descriptor does not exist
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(82, SpvOpKill, 0), 0)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: early terminator instruction does not identify an early terminator
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(5, SpvOpSelectionMerge, 0), 0)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: no wrapper function is available
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(9, SpvOpUnreachable, 0), 0)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: returned value does not exist
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(62, SpvOpKill, 0), 1000)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: returned value does not have a type
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(62, SpvOpKill, 0), 61)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: returned value type does not match
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(62, SpvOpKill, 0), 91)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: returned value is not available
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(62, SpvOpKill, 0), 81)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Bad: the OpKill being targeted is in the only available wrapper; we cannot
+  // have the wrapper call itself.
+  ASSERT_FALSE(TransformationWrapEarlyTerminatorInFunction(
+                   100, MakeInstructionDescriptor(31, SpvOpKill, 0), 0)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationWrapEarlyTerminatorInFunctionTest, Apply) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpExtension "SPV_KHR_terminate_invocation"
+          %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
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 0
+
+         %20 = OpTypeFunction %2 %6
+         %21 = OpTypeFunction %6
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %11 None
+               OpSwitch %7 %11 0 %8 1 %9 2 %10
+          %8 = OpLabel
+               OpKill
+          %9 = OpLabel
+               OpUnreachable
+         %10 = OpLabel
+               OpTerminateInvocation
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         %30 = OpFunction %2 None %3
+         %31 = OpLabel
+               OpKill
+               OpFunctionEnd
+
+         %40 = OpFunction %2 None %3
+         %41 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+
+         %50 = OpFunction %2 None %3
+         %51 = OpLabel
+               OpTerminateInvocation
+               OpFunctionEnd
+
+         %60 = OpFunction %2 None %3
+         %61 = OpLabel
+               OpBranch %62
+         %62 = OpLabel
+               OpKill
+               OpFunctionEnd
+
+         %70 = OpFunction %6 None %21
+         %71 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+
+         %80 = OpFunction %2 None %20
+         %81 = OpFunctionParameter %6
+         %82 = OpLabel
+               OpTerminateInvocation
+               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);
+
+  for (auto& transformation :
+       {TransformationWrapEarlyTerminatorInFunction(
+            100, MakeInstructionDescriptor(8, SpvOpKill, 0), 0),
+        TransformationWrapEarlyTerminatorInFunction(
+            101, MakeInstructionDescriptor(9, SpvOpUnreachable, 0), 0),
+        TransformationWrapEarlyTerminatorInFunction(
+            102, MakeInstructionDescriptor(10, SpvOpTerminateInvocation, 0), 0),
+        TransformationWrapEarlyTerminatorInFunction(
+            103, MakeInstructionDescriptor(62, SpvOpKill, 0), 0),
+        TransformationWrapEarlyTerminatorInFunction(
+            104, MakeInstructionDescriptor(71, SpvOpUnreachable, 0), 7),
+        TransformationWrapEarlyTerminatorInFunction(
+            105, MakeInstructionDescriptor(82, SpvOpTerminateInvocation, 0),
+            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
+               OpExtension "SPV_KHR_terminate_invocation"
+          %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
+          %6 = OpTypeInt 32 1
+          %7 = OpConstant %6 0
+
+         %20 = OpTypeFunction %2 %6
+         %21 = OpTypeFunction %6
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %11 None
+               OpSwitch %7 %11 0 %8 1 %9 2 %10
+          %8 = OpLabel
+        %100 = OpFunctionCall %2 %30
+               OpReturn
+          %9 = OpLabel
+        %101 = OpFunctionCall %2 %40
+               OpReturn
+         %10 = OpLabel
+        %102 = OpFunctionCall %2 %50
+               OpReturn
+         %11 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+         %30 = OpFunction %2 None %3
+         %31 = OpLabel
+               OpKill
+               OpFunctionEnd
+
+         %40 = OpFunction %2 None %3
+         %41 = OpLabel
+               OpUnreachable
+               OpFunctionEnd
+
+         %50 = OpFunction %2 None %3
+         %51 = OpLabel
+               OpTerminateInvocation
+               OpFunctionEnd
+
+         %60 = OpFunction %2 None %3
+         %61 = OpLabel
+               OpBranch %62
+         %62 = OpLabel
+        %103 = OpFunctionCall %2 %30
+               OpReturn
+               OpFunctionEnd
+
+         %70 = OpFunction %6 None %21
+         %71 = OpLabel
+        %104 = OpFunctionCall %2 %40
+               OpReturnValue %7
+               OpFunctionEnd
+
+         %80 = OpFunction %2 None %20
+         %81 = OpFunctionParameter %6
+         %82 = OpLabel
+        %105 = OpFunctionCall %2 %50
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_wrap_region_in_selection_test.cpp b/test/fuzz/transformation_wrap_region_in_selection_test.cpp
new file mode 100644
index 0000000..9669c41
--- /dev/null
+++ b/test/fuzz/transformation_wrap_region_in_selection_test.cpp
@@ -0,0 +1,266 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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_region_in_selection.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(TransformationWrapRegionInSelectionTest, 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
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpBranch %6
+
+          %6 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %8 %11 %12
+         %11 = OpLabel
+               OpReturn
+
+         %12 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %8 %13 %14
+         %13 = OpLabel
+               OpBranch %15
+         %14 = OpLabel
+               OpBranch %15
+         %15 = OpLabel
+               OpBranch %16
+
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+          %9 = OpFunction %2 None %3
+         %10 = OpLabel
+               OpBranch %20
+
+         %20 = OpLabel
+               OpLoopMerge %23 %22 None
+               OpBranch %21
+         %21 = OpLabel
+               OpBranchConditional %8 %24 %23
+         %24 = OpLabel
+               OpBranch %22
+
+         ; continue target
+         %22 = OpLabel
+               OpLoopMerge %25 %28 None
+               OpBranchConditional %8 %27 %25
+         %27 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %22
+         %25 = OpLabel
+               OpBranch %20
+
+         ; merge block
+         %23 = OpLabel
+               OpBranch %26
+
+         %26 = 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);
+
+  // Boolean constant does not exist.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(5, 6, false)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Irrelevant constant does not exist.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(5, 6, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  transformation_context.GetFactManager()->AddFactIdIsIrrelevant(8);
+
+  // Block ids are invalid.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(100, 6, true)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationWrapRegionInSelection(5, 100, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Blocks are from different functions.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(5, 10, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Header block candidate does not dominate merge block candidate.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(13, 16, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Header block candidate does not *strictly* dominate merge block candidate.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(5, 5, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Merge block candidate does not postdominate header block candidate.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(5, 16, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Header block candidate is already a header block of some other construct.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(12, 16, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Header block's terminator is not an OpBranch.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(21, 24, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Merge block candidate is already a merge block of some other construct.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(5, 15, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Header block candidate and merge block candidate are in different
+  // constructs.
+  ASSERT_FALSE(TransformationWrapRegionInSelection(10, 21, true)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationWrapRegionInSelection(24, 25, true)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationWrapRegionInSelection(24, 22, true)
+                   .IsApplicable(context.get(), transformation_context));
+  ASSERT_FALSE(TransformationWrapRegionInSelection(24, 27, true)
+                   .IsApplicable(context.get(), transformation_context));
+
+  {
+    // Header block candidate can be a merge block of some existing construct.
+    TransformationWrapRegionInSelection transformation(15, 16, true);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    // Merge block candidate can be a header block of some existing construct.
+    TransformationWrapRegionInSelection transformation(5, 6, true);
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    ApplyAndCheckFreshIds(transformation, context.get(),
+                          &transformation_context);
+    ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(
+        context.get(), validator_options, kConsoleMessageConsumer));
+  }
+  {
+    // Wrap a loop construct.
+    TransformationWrapRegionInSelection transformation(10, 26, true);
+    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
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %7 = OpTypeBool
+          %8 = OpConstantTrue %7
+
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpSelectionMerge %6 None
+               OpBranchConditional %8 %6 %6
+
+          %6 = OpLabel
+               OpSelectionMerge %12 None
+               OpBranchConditional %8 %11 %12
+         %11 = OpLabel
+               OpReturn
+
+         %12 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %8 %13 %14
+         %13 = OpLabel
+               OpBranch %15
+         %14 = OpLabel
+               OpBranch %15
+
+         %15 = OpLabel
+               OpSelectionMerge %16 None
+               OpBranchConditional %8 %16 %16
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+          %9 = OpFunction %2 None %3
+         %10 = OpLabel
+               OpSelectionMerge %26 None
+               OpBranchConditional %8 %20 %20
+
+         %20 = OpLabel
+               OpLoopMerge %23 %22 None
+               OpBranch %21
+         %21 = OpLabel
+               OpBranchConditional %8 %24 %23
+         %24 = OpLabel
+               OpBranch %22
+
+         ; continue target
+         %22 = OpLabel
+               OpLoopMerge %25 %28 None
+               OpBranchConditional %8 %27 %25
+         %27 = OpLabel
+               OpBranch %28
+         %28 = OpLabel
+               OpBranch %22
+         %25 = OpLabel
+               OpBranch %20
+
+         ; merge block
+         %23 = OpLabel
+               OpBranch %26
+
+         %26 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/uniform_buffer_element_descriptor_test.cpp b/test/fuzz/uniform_buffer_element_descriptor_test.cpp
index 6c6d52a..dd24794 100644
--- a/test/fuzz/uniform_buffer_element_descriptor_test.cpp
+++ b/test/fuzz/uniform_buffer_element_descriptor_test.cpp
@@ -13,7 +13,8 @@
 // limitations under the License.
 
 #include "source/fuzz/uniform_buffer_element_descriptor.h"
-#include "test/fuzz/fuzz_test_util.h"
+
+#include "gtest/gtest.h"
 
 namespace spvtools {
 namespace fuzz {
diff --git a/test/link/binary_version_test.cpp b/test/link/binary_version_test.cpp
index 0ceeeba..80aab0f 100644
--- a/test/link/binary_version_test.cpp
+++ b/test/link/binary_version_test.cpp
@@ -27,21 +27,21 @@
   spvtest::Binaries binaries = {
       {
           SpvMagicNumber,
-          0x00000300u,
+          0x00010300u,
           SPV_GENERATOR_CODEPLAY,
           1u,  // NOTE: Bound
           0u   // NOTE: Schema; reserved
       },
       {
           SpvMagicNumber,
-          0x00000600u,
+          0x00010500u,
           SPV_GENERATOR_CODEPLAY,
           1u,  // NOTE: Bound
           0u   // NOTE: Schema; reserved
       },
       {
           SpvMagicNumber,
-          0x00000100u,
+          0x00010100u,
           SPV_GENERATOR_CODEPLAY,
           1u,  // NOTE: Bound
           0u   // NOTE: Schema; reserved
@@ -53,7 +53,7 @@
   ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary));
   EXPECT_THAT(GetErrorMessage(), std::string());
 
-  EXPECT_EQ(0x00000600u, linked_binary[1]);
+  EXPECT_EQ(0x00010500u, linked_binary[1]);
 }
 
 }  // namespace
diff --git a/test/opcode_table_get_test.cpp b/test/opcode_table_get_test.cpp
index 5ebd6c1..4ff67d9 100644
--- a/test/opcode_table_get_test.cpp
+++ b/test/opcode_table_get_test.cpp
@@ -21,7 +21,7 @@
 using GetTargetOpcodeTableGetTest = ::testing::TestWithParam<spv_target_env>;
 using ::testing::ValuesIn;
 
-TEST_P(GetTargetOpcodeTableGetTest, SanityCheck) {
+TEST_P(GetTargetOpcodeTableGetTest, IntegrityCheck) {
   spv_opcode_table table;
   ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&table, GetParam()));
   ASSERT_NE(0u, table->count);
diff --git a/test/operand_test.cpp b/test/operand_test.cpp
index 4e2c321..ec45da5 100644
--- a/test/operand_test.cpp
+++ b/test/operand_test.cpp
@@ -42,9 +42,16 @@
   // None has no string, so don't test it.
   EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE);
   // Start testing at enum with value 1, skipping None.
-  for (int i = 1; i < int(SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE); i++) {
-    EXPECT_NE(nullptr, spvOperandTypeStr(static_cast<spv_operand_type_t>(i)))
-        << " Operand type " << i;
+  for (int i = 1; i < int(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES); i++) {
+    const auto type = static_cast<spv_operand_type_t>(i);
+    if (spvOperandIsVariable(type)) {
+      EXPECT_STREQ("unknown", spvOperandTypeStr(type))
+          << " variable type " << i << " has a name '"
+          << spvOperandTypeStr(type) << "'when it should not";
+    } else {
+      EXPECT_STRNE("unknown", spvOperandTypeStr(type))
+          << " operand type " << i << " has no name when it should";
+    }
   }
 }
 
@@ -71,5 +78,46 @@
       spvOperandIsConcreteMask(SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS));
 }
 
+TEST(OperandType, NoneTypeClassification) {
+  EXPECT_FALSE(spvOperandIsConcrete(SPV_OPERAND_TYPE_NONE));
+  EXPECT_FALSE(spvOperandIsOptional(SPV_OPERAND_TYPE_NONE));
+  EXPECT_FALSE(spvOperandIsVariable(SPV_OPERAND_TYPE_NONE));
+}
+
+TEST(OperandType, EndSentinelTypeClassification) {
+  EXPECT_FALSE(spvOperandIsConcrete(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES));
+  EXPECT_FALSE(spvOperandIsOptional(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES));
+  EXPECT_FALSE(spvOperandIsVariable(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES));
+}
+
+TEST(OperandType, WidthForcingTypeClassification) {
+  EXPECT_FALSE(spvOperandIsConcrete(SPV_FORCE_32BIT_spv_operand_type_t));
+  EXPECT_FALSE(spvOperandIsOptional(SPV_FORCE_32BIT_spv_operand_type_t));
+  EXPECT_FALSE(spvOperandIsVariable(SPV_FORCE_32BIT_spv_operand_type_t));
+}
+
+TEST(OperandType, EachTypeIsEitherConcreteOrOptionalNotBoth) {
+  EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE);
+  // Start testing at enum with value 1, skipping None.
+  for (int i = 1; i < int(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES); i++) {
+    const auto type = static_cast<spv_operand_type_t>(i);
+    EXPECT_NE(spvOperandIsConcrete(type), spvOperandIsOptional(type))
+        << " operand type " << int(type) << " concrete? "
+        << int(spvOperandIsConcrete(type)) << " optional? "
+        << int(spvOperandIsOptional(type));
+  }
+}
+
+TEST(OperandType, EachVariableTypeIsOptional) {
+  EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE);
+  // Start testing at enum with value 1, skipping None.
+  for (int i = 1; i < int(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES); i++) {
+    const auto type = static_cast<spv_operand_type_t>(i);
+    if (spvOperandIsVariable(type)) {
+      EXPECT_TRUE(spvOperandIsOptional(type)) << " variable type " << int(type);
+    }
+  }
+}
+
 }  // namespace
 }  // namespace spvtools
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 21a6529..3426958 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -78,7 +78,6 @@
        pass_remove_duplicates_test.cpp
        pass_utils.cpp
        private_to_local_test.cpp
-       process_lines_test.cpp
        propagator_test.cpp
        reduce_load_size_test.cpp
        redundancy_elimination_test.cpp
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
index 125543d..972e6e5 100644
--- a/test/opt/aggressive_dead_code_elim_test.cpp
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -7525,6 +7525,62 @@
   SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
 }
 
+TEST_F(AggressiveDCETest, KeepDebugScopeParent) {
+  // Verify that local variable tc and its store are kept by DebugDeclare.
+  //
+  // Same shader source as DebugInfoInFunctionKeepStoreVarElim. The SPIR-V
+  // has just been inlined.
+
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %out_var_SV_TARGET0
+               OpExecutionMode %main OriginUpperLeft
+         %11 = OpString "float"
+         %16 = OpString "t.hlsl"
+         %19 = OpString "src.main"
+               OpName %out_var_SV_TARGET0 "out.var.SV_TARGET0"
+               OpName %main "main"
+               OpDecorate %out_var_SV_TARGET0 Location 0
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %v4float = OpTypeVector %float 4
+          %7 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+         %26 = OpTypeFunction %v4float
+%out_var_SV_TARGET0 = OpVariable %_ptr_Output_v4float Output
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+         %33 = OpExtInst %void %1 DebugInfoNone
+         %13 = OpExtInst %void %1 DebugTypeBasic %11 %uint_32 Float
+         %14 = OpExtInst %void %1 DebugTypeVector %13 4
+         %15 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %14
+         %17 = OpExtInst %void %1 DebugSource %16
+         %18 = OpExtInst %void %1 DebugCompilationUnit 1 4 %17 HLSL
+         %20 = OpExtInst %void %1 DebugFunction %19 %15 %17 1 1 %18 %19 FlagIsProtected|FlagIsPrivate 2 %33
+         %22 = OpExtInst %void %1 DebugLexicalBlock %17 2 1 %20
+       %main = OpFunction %void None %23
+         %24 = OpLabel
+         %31 = OpVariable %_ptr_Function_v4float Function
+; CHECK: [[block:%\w+]] = OpExtInst %void %1 DebugLexicalBlock
+; CHECK: DebugScope [[block]]
+         %34 = OpExtInst %void %1 DebugScope %22
+               OpLine %16 3 5
+               OpStore %31 %7
+               OpStore %out_var_SV_TARGET0 %7
+         %35 = OpExtInst %void %1 DebugNoScope
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Check that logical addressing required
diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp
index 52a291c..ef73435 100644
--- a/test/opt/ccp_test.cpp
+++ b/test/opt/ccp_test.cpp
@@ -598,6 +598,11 @@
 %int_ptr_Input = OpTypePointer Input %int
 %in = OpVariable %int_ptr_Input Input
 %undef = OpUndef %int
+
+; Although no constants are propagated in this function, the propagator
+; generates a new %true value while visiting conditional statements.
+; CHECK: %true = OpConstantTrue %bool
+
 %functy = OpTypeFunction %void
 %func = OpFunction %void None %functy
 %1 = OpLabel
@@ -639,8 +644,8 @@
 OpFunctionEnd
 )";
 
-  auto res = SinglePassRunToBinary<CCPPass>(text, true);
-  EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange);
+  auto result = SinglePassRunAndMatch<CCPPass>(text, true);
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
 }
 
 TEST_F(CCPTest, UndefInPhi) {
@@ -1056,6 +1061,153 @@
   SinglePassRunAndMatch<CCPPass>(text, true);
 }
 
+// Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3636
+TEST_F(CCPTest, CCPNoChangeFailure) {
+  const std::string text = 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 = OpConstant %6 2
+         %13 = OpConstant %6 4
+         %21 = OpConstant %6 1
+         %10 = OpTypeBool
+         %17 = OpTypePointer Function %6
+
+; CCP is generating two new constants during propagation that end up being
+; dead because they cannot be replaced anywhere in the IR.  CCP was wrongly
+; considering the IR to be unmodified because of this.
+; CHECK: %true = OpConstantTrue %bool
+; CHECK: %int_3 = OpConstant %int 3
+
+          %4 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+         %23 = OpPhi %6 %7 %11 %20 %15
+          %9 = OpSLessThan %10 %23 %13
+               OpLoopMerge %8 %15 None
+               OpBranchConditional %9 %15 %8
+         %15 = OpLabel
+         %20 = OpIAdd %6 %23 %21
+               OpBranch %5
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndMatch<CCPPass>(text, true);
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
+
+// Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3738
+// Similar to the previous one but more than one constant is generated in a
+// single call to the instruction folder.
+TEST_F(CCPTest, CCPNoChangeFailureSeveralConstantsDuringFolding) {
+  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
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+       %bool = OpTypeBool
+     %v3bool = OpTypeVector %bool 3
+    %float_0 = OpConstant %float 0
+         %12 = OpConstantComposite %v3float %float_0 %float_0 %float_0
+%float_0_300000012 = OpConstant %float 0.300000012
+         %14 = OpConstantComposite %v3float %float_0_300000012 %float_0_300000012 %float_0_300000012
+
+; CCP is generating several constants during a single instruction evaluation.
+; When folding %19, it generates the constants %true and %24.  They are dead
+; because they cannot be replaced anywhere in the IR.  CCP was wrongly
+; considering the IR to be unmodified because of this.
+;
+; CHECK: %true = OpConstantTrue %bool
+; CHECK: %24 = OpConstantComposite %v3bool %true %true %true
+; CHECK: %float_1 = OpConstant %float 1
+; CHECK: %float_0_699999988 = OpConstant %float 0.699999988
+
+          %2 = OpFunction %void None %4
+         %15 = OpLabel
+               OpBranch %16
+         %16 = OpLabel
+         %17 = OpPhi %v3float %12 %15 %14 %18
+         %19 = OpFOrdLessThan %v3bool %17 %14
+         %20 = OpAll %bool %19
+               OpLoopMerge %21 %18 None
+               OpBranchConditional %20 %18 %21
+         %18 = OpLabel
+               OpBranch %16
+         %21 = OpLabel
+         %22 = OpExtInst %v3float %1 FMix %12 %17 %14
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndMatch<CCPPass>(text, true);
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
+
+// Test from https://github.com/KhronosGroup/SPIRV-Tools/issues/3991
+// Similar to the previous one but constants are created even when no
+// instruction are ever folded during propagation.
+TEST_F(CCPTest, CCPNoChangeFailureWithUnfoldableInstr) {
+  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
+      %float = OpTypeFloat 32
+    %v3float = OpTypeVector %float 3
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+       %bool = OpTypeBool
+    %float_0 = OpConstant %float 0
+         %11 = OpConstantComposite %v3float %float_0 %float_0 %float_0
+%float_0_300000012 = OpConstant %float 0.300000012
+         %13 = OpConstantComposite %v3float %float_0_300000012 %float_0_300000012 %float_0_300000012
+
+; CCP generates two constants when trying to fold an instruction, which it
+; ultimately fails to fold. The instruction folder in CCP was only
+; checking for newly added constants if the instruction folds successfully.
+;
+; CHECK: %float_1 = OpConstant %float 1
+; CHECK: %float_0_699999988 = OpConstant %float 0.69999998
+
+          %2 = OpFunction %void None %4
+         %14 = OpLabel
+         %15 = OpBitcast %uint %float_0_300000012
+         %16 = OpUGreaterThan %bool %15 %uint_0
+               OpBranch %17
+         %17 = OpLabel
+         %18 = OpPhi %v3float %11 %14 %13 %19
+               OpLoopMerge %20 %19 None
+               OpBranchConditional %16 %19 %20
+         %19 = OpLabel
+               OpBranch %17
+         %20 = OpLabel
+         %21 = OpExtInst %v3float %1 FMix %11 %18 %13
+               OpReturn
+               OpFunctionEnd
+)";
+
+  auto result = SinglePassRunAndMatch<CCPPass>(text, true);
+  EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/compact_ids_test.cpp b/test/opt/compact_ids_test.cpp
index b1e4b2c..ba31d84 100644
--- a/test/opt/compact_ids_test.cpp
+++ b/test/opt/compact_ids_test.cpp
@@ -92,6 +92,42 @@
   SinglePassRunAndCheck<CompactIdsPass>(before, after, false, false);
 }
 
+TEST_F(CompactIdsTest, DebugScope) {
+  const std::string text =
+      R"(OpCapability Addresses
+OpCapability Kernel
+OpCapability GenericPointer
+OpCapability Linkage
+%5 = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Physical32 OpenCL
+OpEntryPoint Kernel %3 "simple_kernel"
+%2 = OpString "test"
+%99 = OpTypeInt 32 0
+%10 = OpTypeVector %99 2
+%20 = OpConstant %99 2
+%30 = OpTypeArray %99 %20
+%40 = OpTypeVoid
+%50 = OpTypeFunction %40
+%11 = OpExtInst %40 %5 DebugSource %2
+%12 = OpExtInst %40 %5 DebugCompilationUnit 1 4 %11 HLSL
+%13 = OpExtInst %40 %5 DebugTypeFunction FlagIsProtected|FlagIsPrivate %40
+
+; CHECK: [[fn:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugFunction
+%14 = OpExtInst %40 %5 DebugFunction %2 %13 %11 0 0 %12 %2 FlagIsProtected|FlagIsPrivate 0 %3
+ %3 = OpFunction %40 None %50
+%70 = OpLabel
+
+; CHECK: DebugScope [[fn]]
+%19 = OpExtInst %40 %5 DebugScope %14
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  SinglePassRunAndMatch<CompactIdsPass>(text, true);
+}
+
 TEST(CompactIds, InstructionResultIsUpdated) {
   // For https://github.com/KhronosGroup/SPIRV-Tools/issues/827
   // In that bug, the compact Ids pass was directly updating the result Id
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
index 3abb53d..41ce31d 100644
--- a/test/opt/dead_branch_elim_test.cpp
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -3377,7 +3377,7 @@
 %18 = OpLabel
 
 ; CHECK: DebugScope [[bb3]]
-; CHECK-NOT: OpLine {{%\w+}} 3 0
+; CHECK: OpLine {{%\w+}} 3 0
 ; CHECK: DebugValue [[dbg_foo]] [[value]]
 ; CHECK: OpLine {{%\w+}} 4 0
 ; CHECK: OpStore %gl_FragColor [[value]]
diff --git a/test/opt/dead_insert_elim_test.cpp b/test/opt/dead_insert_elim_test.cpp
index 8ae6894..9ea948a 100644
--- a/test/opt/dead_insert_elim_test.cpp
+++ b/test/opt/dead_insert_elim_test.cpp
@@ -563,6 +563,113 @@
                                             after_predefs + after, true, true);
 }
 
+TEST_F(DeadInsertElimTest, DebugInsertAfterInsertElim) {
+  // With two insertions to the same offset, the first is dead.
+  //
+  // Note: The SPIR-V assembly has had store/load elimination
+  // performed to allow the inserts and extracts to directly
+  // reference each other.
+  //
+  // #version 450
+  //
+  // layout (location=0) in float In0;
+  // layout (location=1) in float In1;
+  // layout (location=2) in vec2 In2;
+  // layout (location=0) out vec4 OutColor;
+  //
+  // void main()
+  // {
+  //     vec2 v = In2;
+  //     v.x = In0 + In1; // dead
+  //     v.x = 0.0;
+  //     OutColor = v.xyxy;
+  // }
+
+  const std::string text =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+%ext = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+%file_name = OpString "test"
+%float_name = OpString "float"
+%main_name = OpString "main"
+%f_name = OpString "f"
+OpName %main "main"
+OpName %In2 "In2"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %OutColor "OutColor"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_n"
+OpName %_ ""
+OpDecorate %In2 Location 2
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %OutColor Location 0
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%In2 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In0 = OpVariable %_ptr_Input_float Input
+%In1 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%int = OpTypeInt 32 1
+%_Globals_ = OpTypeStruct %uint %int
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+
+%nullexpr = OpExtInst %void %ext DebugExpression
+%src = OpExtInst %void %ext DebugSource %file_name
+%cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL
+%dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 Float
+%dbg_v2f = OpExtInst %void %ext DebugTypeVector %dbg_tf 2
+%main_ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %void
+%dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 0 %main
+%dbg_foo = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_v2f %src 0 0 %dbg_main FlagIsLocal
+
+%main = OpFunction %void None %11
+%25 = OpLabel
+%26 = OpLoad %v2float %In2
+%27 = OpLoad %float %In0
+%28 = OpLoad %float %In1
+%29 = OpFAdd %float %27 %28
+
+; CHECK:      [[repl:%\w+]] = OpLoad %v2float %In2
+; CHECK-NOT:  OpCompositeInsert
+; CHECK:      DebugValue {{%\w+}} [[repl:%\w+]]
+; CHECK-NEXT: OpCompositeInsert %v2float %float_0 [[repl]] 0
+%35 = OpCompositeInsert %v2float %29 %26 0
+%value = OpExtInst %void %ext DebugValue %dbg_foo %35 %nullexpr
+%37 = OpCompositeInsert %v2float %float_0 %35 0
+
+%33 = OpVectorShuffle %v4float %37 %37 0 1 0 1
+OpStore %OutColor %33
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<DeadInsertElimPass>(text, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 
diff --git a/test/opt/debug_info_manager_test.cpp b/test/opt/debug_info_manager_test.cpp
index 82890f7..911331a 100644
--- a/test/opt/debug_info_manager_test.cpp
+++ b/test/opt/debug_info_manager_test.cpp
@@ -354,6 +354,116 @@
             200);
 }
 
+TEST(DebugInfoManager, GetDebugFunction_InlinedAway) {
+  // struct PS_INPUT
+  // {
+  //   float4 iColor : COLOR;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 oColor : SV_Target0;
+  // };
+  //
+  // float4 foo(float4 ic)
+  // {
+  //   float4 c = ic / 2.0;
+  //   return c;
+  // }
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //   float4 ic = i.iColor;
+  //   ps_output.oColor = foo(ic);
+  //   return ps_output;
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %in_var_COLOR %out_var_SV_Target0
+               OpExecutionMode %MainPs OriginUpperLeft
+         %15 = OpString "foo2.frag"
+         %19 = OpString "PS_OUTPUT"
+         %23 = OpString "float"
+         %26 = OpString "oColor"
+         %28 = OpString "PS_INPUT"
+         %31 = OpString "iColor"
+         %33 = OpString "foo"
+         %37 = OpString "c"
+         %39 = OpString "ic"
+         %42 = OpString "src.MainPs"
+         %47 = OpString "ps_output"
+         %50 = OpString "i"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %out_var_SV_Target0 "out.var.SV_Target0"
+               OpName %MainPs "MainPs"
+               OpDecorate %in_var_COLOR Location 0
+               OpDecorate %out_var_SV_Target0 Location 0
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+   %uint_128 = OpConstant %uint 128
+     %uint_0 = OpConstant %uint 0
+         %52 = OpTypeFunction %void
+%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
+%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
+  %float_0_5 = OpConstant %float 0.5
+        %130 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5
+        %115 = OpExtInst %void %1 DebugInfoNone
+         %49 = OpExtInst %void %1 DebugExpression
+         %17 = OpExtInst %void %1 DebugSource %15
+         %18 = OpExtInst %void %1 DebugCompilationUnit 1 4 %17 HLSL
+         %21 = OpExtInst %void %1 DebugTypeComposite %19 Structure %17 6 1 %18 %19 %uint_128 FlagIsProtected|FlagIsPrivate %22
+         %24 = OpExtInst %void %1 DebugTypeBasic %23 %uint_32 Float
+         %25 = OpExtInst %void %1 DebugTypeVector %24 4
+         %22 = OpExtInst %void %1 DebugTypeMember %26 %25 %17 8 5 %21 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
+         %29 = OpExtInst %void %1 DebugTypeComposite %28 Structure %17 1 1 %18 %28 %uint_128 FlagIsProtected|FlagIsPrivate %30
+         %30 = OpExtInst %void %1 DebugTypeMember %31 %25 %17 3 5 %29 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
+         %32 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %25 %25
+         %34 = OpExtInst %void %1 DebugFunction %33 %32 %17 11 1 %18 %33 FlagIsProtected|FlagIsPrivate 12 %115
+         %36 = OpExtInst %void %1 DebugLexicalBlock %17 12 1 %34
+         %38 = OpExtInst %void %1 DebugLocalVariable %37 %25 %17 13 12 %36 FlagIsLocal
+         %41 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %21 %29
+         %43 = OpExtInst %void %1 DebugFunction %42 %41 %17 17 1 %18 %42 FlagIsProtected|FlagIsPrivate 18 %115
+         %45 = OpExtInst %void %1 DebugLexicalBlock %17 18 1 %43
+         %46 = OpExtInst %void %1 DebugLocalVariable %39 %25 %17 20 12 %45 FlagIsLocal
+         %48 = OpExtInst %void %1 DebugLocalVariable %47 %21 %17 19 15 %45 FlagIsLocal
+        %107 = OpExtInst %void %1 DebugInlinedAt 21 %45
+     %MainPs = OpFunction %void None %52
+         %53 = OpLabel
+         %57 = OpLoad %v4float %in_var_COLOR
+        %131 = OpExtInst %void %1 DebugScope %45
+               OpLine %15 20 12
+        %117 = OpExtInst %void %1 DebugValue %46 %57 %49
+        %132 = OpExtInst %void %1 DebugScope %36 %107
+               OpLine %15 13 19
+        %112 = OpFMul %v4float %57 %130
+               OpLine %15 13 12
+        %116 = OpExtInst %void %1 DebugValue %38 %112 %49
+        %133 = OpExtInst %void %1 DebugScope %45
+        %128 = OpExtInst %void %1 DebugValue %48 %112 %49 %int_0
+        %134 = OpExtInst %void %1 DebugNoScope
+               OpStore %out_var_SV_Target0 %112
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  DebugInfoManager manager(context.get());
+
+  EXPECT_EQ(manager.GetDebugFunction(115), nullptr);
+}
+
 TEST(DebugInfoManager, CloneDebugInlinedAt) {
   const std::string text = R"(
                OpCapability Shader
@@ -484,7 +594,7 @@
   auto* dbg_info_mgr = context->get_debug_info_mgr();
   auto* def_use_mgr = context->get_def_use_mgr();
 
-  EXPECT_TRUE(dbg_info_mgr->IsDebugDeclared(100));
+  EXPECT_TRUE(dbg_info_mgr->IsVariableDebugDeclared(100));
   EXPECT_EQ(def_use_mgr->GetDef(36)->GetOpenCL100DebugOpcode(),
             OpenCLDebugInfo100DebugDeclare);
   EXPECT_EQ(def_use_mgr->GetDef(37)->GetOpenCL100DebugOpcode(),
@@ -496,7 +606,7 @@
   EXPECT_EQ(def_use_mgr->GetDef(36), nullptr);
   EXPECT_EQ(def_use_mgr->GetDef(37), nullptr);
   EXPECT_EQ(def_use_mgr->GetDef(38), nullptr);
-  EXPECT_FALSE(dbg_info_mgr->IsDebugDeclared(100));
+  EXPECT_FALSE(dbg_info_mgr->IsVariableDebugDeclared(100));
 }
 
 }  // namespace
diff --git a/test/opt/eliminate_dead_functions_test.cpp b/test/opt/eliminate_dead_functions_test.cpp
index 2f8fa9a..96ecdc6 100644
--- a/test/opt/eliminate_dead_functions_test.cpp
+++ b/test/opt/eliminate_dead_functions_test.cpp
@@ -344,6 +344,101 @@
   SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, false);
 }
 
+TEST_F(EliminateDeadFunctionsBasicTest, NonSemanticInfoPersists) {
+  const std::string text = R"(
+; CHECK: [[import:%\w+]] = OpExtInstImport
+; CHECK: [[void:%\w+]] = OpTypeVoid
+; CHECK-NOT: OpExtInst [[void]] [[import]] 1
+; CHECK: OpExtInst [[void]] [[import]] 2
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%foo = OpFunction %void None %void_fn
+%foo_entry = OpLabel
+%non_semantic1 = OpExtInst %void %ext 1
+OpReturn
+OpFunctionEnd
+%non_semantic2 = OpExtInst %void %ext 2
+)";
+
+  SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true);
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, NonSemanticInfoRemoveDependent) {
+  const std::string text = R"(
+; CHECK: [[import:%\w+]] = OpExtInstImport
+; CHECK: [[void:%\w+]] = OpTypeVoid
+; CHECK-NOT: OpExtInst [[void]] [[import]] 1
+; CHECK-NOT: OpExtInst [[void]] [[import]] 2
+; CHECK: OpExtInst [[void]] [[import]] 3
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%foo = OpFunction %void None %void_fn
+%foo_entry = OpLabel
+%non_semantic1 = OpExtInst %void %ext 1
+OpReturn
+OpFunctionEnd
+%non_semantic2 = OpExtInst %void %ext 2 %foo
+%non_semantic3 = OpExtInst %void %ext 3 
+)";
+
+  SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true);
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, NonSemanticInfoRemoveDependentTree) {
+  const std::string text = R"(
+; CHECK: [[import:%\w+]] = OpExtInstImport
+; CHECK: [[void:%\w+]] = OpTypeVoid
+; CHECK-NOT: OpExtInst [[void]] [[import]] 1
+; CHECK-NOT: OpExtInst [[void]] [[import]] 2
+; CHECK: OpExtInst [[void]] [[import]] 3
+; CHECK-NOT: OpExtInst [[void]] [[import]] 4
+; CHECK-NOT: OpExtInst [[void]] [[import]] 5
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%foo = OpFunction %void None %void_fn
+%foo_entry = OpLabel
+%non_semantic1 = OpExtInst %void %ext 1
+OpReturn
+OpFunctionEnd
+%non_semantic2 = OpExtInst %void %ext 2 %foo
+%non_semantic3 = OpExtInst %void %ext 3 
+%non_semantic4 = OpExtInst %void %ext 4 %non_semantic2
+%non_semantic5 = OpExtInst %void %ext 5 %non_semantic4
+)";
+
+  SinglePassRunAndMatch<EliminateDeadFunctionsPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
index beb26f2..bb6098c 100644
--- a/test/opt/fold_test.cpp
+++ b/test/opt/fold_test.cpp
@@ -3464,7 +3464,18 @@
             "%3 = OpCompositeExtract %float %2 4\n" +
             "OpReturn\n" +
             "OpFunctionEnd",
-        3, 0)
+        3, 0),
+    // Test case 14: https://github.com/KhronosGroup/SPIRV-Tools/issues/3631
+    // Extract the component right after the vector constituent.
+    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 2\n" +
+            "OpReturn\n" +
+            "OpFunctionEnd",
+        4, INT_0_ID)
 ));
 
 INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
diff --git a/test/opt/function_test.cpp b/test/opt/function_test.cpp
index 38ab298..af25bac 100644
--- a/test/opt/function_test.cpp
+++ b/test/opt/function_test.cpp
@@ -29,6 +29,60 @@
 
 using ::testing::Eq;
 
+TEST(FunctionTest, HasEarlyReturn) {
+  std::string shader = R"(
+          OpCapability Shader
+     %1 = OpExtInstImport "GLSL.std.450"
+          OpMemoryModel Logical GLSL450
+          OpEntryPoint Vertex %6 "main"
+
+; Types
+     %2 = OpTypeBool
+     %3 = OpTypeVoid
+     %4 = OpTypeFunction %3
+
+; Constants
+     %5 = OpConstantTrue %2
+
+; main function without early return
+     %6 = OpFunction %3 None %4
+     %7 = OpLabel
+          OpBranch %8
+     %8 = OpLabel
+          OpBranch %9
+     %9 = OpLabel
+          OpBranch %10
+    %10 = OpLabel
+          OpReturn
+          OpFunctionEnd
+
+; function with early return
+    %11 = OpFunction %3 None %4
+    %12 = OpLabel
+          OpSelectionMerge %15 None
+          OpBranchConditional %5 %13 %14
+    %13 = OpLabel
+          OpReturn
+    %14 = OpLabel
+          OpBranch %15
+    %15 = OpLabel
+          OpReturn
+          OpFunctionEnd
+  )";
+
+  const auto context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, shader,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+  // Tests |function| without early return.
+  auto* function = spvtest::GetFunction(context->module(), 6);
+  ASSERT_FALSE(function->HasEarlyReturn());
+
+  // Tests |function| with early return.
+  function = spvtest::GetFunction(context->module(), 11);
+  ASSERT_TRUE(function->HasEarlyReturn());
+}
+
 TEST(FunctionTest, IsNotRecursive) {
   const std::string text = R"(
 OpCapability Shader
@@ -168,6 +222,80 @@
   EXPECT_FALSE(func->IsRecursive());
 }
 
+TEST(FunctionTest, NonSemanticInfoSkipIteration) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%6 = OpExtInst %2 %1 1
+OpReturn
+OpFunctionEnd
+%7 = OpExtInst %2 %1 2
+%8 = OpExtInst %2 %1 3
+)";
+
+  std::unique_ptr<IRContext> ctx =
+      spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                            SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto* func = spvtest::GetFunction(ctx->module(), 4);
+  ASSERT_TRUE(func != nullptr);
+  std::unordered_set<uint32_t> non_semantic_ids;
+  func->ForEachInst(
+      [&non_semantic_ids](const Instruction* inst) {
+        if (inst->opcode() == SpvOpExtInst) {
+          non_semantic_ids.insert(inst->result_id());
+        }
+      },
+      true, false);
+
+  EXPECT_EQ(1, non_semantic_ids.count(6));
+  EXPECT_EQ(0, non_semantic_ids.count(7));
+  EXPECT_EQ(0, non_semantic_ids.count(8));
+}
+
+TEST(FunctionTest, NonSemanticInfoIncludeIteration) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%6 = OpExtInst %2 %1 1
+OpReturn
+OpFunctionEnd
+%7 = OpExtInst %2 %1 2
+%8 = OpExtInst %2 %1 3
+)";
+
+  std::unique_ptr<IRContext> ctx =
+      spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                            SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  auto* func = spvtest::GetFunction(ctx->module(), 4);
+  ASSERT_TRUE(func != nullptr);
+  std::unordered_set<uint32_t> non_semantic_ids;
+  func->ForEachInst(
+      [&non_semantic_ids](const Instruction* inst) {
+        if (inst->opcode() == SpvOpExtInst) {
+          non_semantic_ids.insert(inst->result_id());
+        }
+      },
+      true, true);
+
+  EXPECT_EQ(1, non_semantic_ids.count(6));
+  EXPECT_EQ(1, non_semantic_ids.count(7));
+  EXPECT_EQ(1, non_semantic_ids.count(8));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/graphics_robust_access_test.cpp b/test/opt/graphics_robust_access_test.cpp
index d38571e..4b2cd44 100644
--- a/test/opt/graphics_robust_access_test.cpp
+++ b/test/opt/graphics_robust_access_test.cpp
@@ -1323,8 +1323,8 @@
   // Split the address calculation across two access chains.  Force
   // the transform to walk up the access chains to find the base variable.
   // This time, put the different access chains in different basic blocks.
-  // This sanity checks that we keep the instruction-to-block mapping
-  // consistent.
+  // This is an integrity check to ensure that we keep the instruction-to-block
+  // mapping consistent.
   for (auto* ac : AccessChains()) {
     std::ostringstream shaders;
     shaders << ShaderPreambleAC({"i", "j", "k", "bb1", "bb2", "ssbo_s",
@@ -1387,6 +1387,167 @@
   }
 }
 
+TEST_F(GraphicsRobustAccessTest, bug3813) {
+  // This shader comes from Dawn's
+  // TextureViewSamplingTest.TextureCubeMapOnWholeTexture, converted from GLSL
+  // by glslang.
+  // The pass was inserting a signed 32-bit int type, but not correctly marking
+  // the shader as changed.
+  std::string shader = R"(
+; SPIR-V
+; Version: 1.0
+; Generator: Google Shaderc over Glslang; 10
+; Bound: 46
+; Schema: 0
+       OpCapability Shader
+  %1 = OpExtInstImport "GLSL.std.450"
+       OpMemoryModel Logical GLSL450
+       OpEntryPoint Fragment %4 "main" %12 %29
+       OpExecutionMode %4 OriginUpperLeft
+       OpSource GLSL 450
+       OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+       OpSourceExtension "GL_GOOGLE_include_directive"
+       OpName %4 "main"
+       OpName %8 "sc"
+       OpName %12 "texCoord"
+       OpName %21 "tc"
+       OpName %29 "fragColor"
+       OpName %32 "texture0"
+       OpName %36 "sampler0"
+       OpDecorate %12 Location 0
+       OpDecorate %29 Location 0
+       OpDecorate %32 DescriptorSet 0
+       OpDecorate %32 Binding 1
+       OpDecorate %36 DescriptorSet 0
+       OpDecorate %36 Binding 0
+  %2 = OpTypeVoid
+  %3 = OpTypeFunction %2
+  %6 = OpTypeFloat 32
+  %7 = OpTypePointer Function %6
+  %9 = OpConstant %6 2
+ %10 = OpTypeVector %6 2
+ %11 = OpTypePointer Input %10
+ %12 = OpVariable %11 Input
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 0
+ %15 = OpTypePointer Input %6
+ %19 = OpConstant %6 1
+ %22 = OpConstant %13 1
+ %27 = OpTypeVector %6 4
+ %28 = OpTypePointer Output %27
+ %29 = OpVariable %28 Output
+ %30 = OpTypeImage %6 Cube 0 0 0 1 Unknown
+ %31 = OpTypePointer UniformConstant %30
+ %32 = OpVariable %31 UniformConstant
+ %34 = OpTypeSampler
+ %35 = OpTypePointer UniformConstant %34
+ %36 = OpVariable %35 UniformConstant
+ %38 = OpTypeSampledImage %30
+ %43 = OpTypeVector %6 3
+  %4 = OpFunction %2 None %3
+  %5 = OpLabel
+  %8 = OpVariable %7 Function
+ %21 = OpVariable %7 Function
+ %16 = OpAccessChain %15 %12 %14
+ %17 = OpLoad %6 %16
+ %18 = OpFMul %6 %9 %17
+ %20 = OpFSub %6 %18 %19
+       OpStore %8 %20
+ %23 = OpAccessChain %15 %12 %22
+ %24 = OpLoad %6 %23
+ %25 = OpFMul %6 %9 %24
+ %26 = OpFSub %6 %25 %19
+       OpStore %21 %26
+ %33 = OpLoad %30 %32
+ %37 = OpLoad %34 %36
+ %39 = OpSampledImage %38 %33 %37
+ %40 = OpLoad %6 %21
+ %41 = OpLoad %6 %8
+ %42 = OpFNegate %6 %41
+ %44 = OpCompositeConstruct %43 %19 %40 %42
+ %45 = OpImageSampleImplicitLod %27 %39 %44
+       OpStore %29 %45
+       OpReturn
+       OpFunctionEnd
+)";
+
+  std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %texCoord %fragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpName %main "main"
+OpName %sc "sc"
+OpName %texCoord "texCoord"
+OpName %tc "tc"
+OpName %fragColor "fragColor"
+OpName %texture0 "texture0"
+OpName %sampler0 "sampler0"
+OpDecorate %texCoord Location 0
+OpDecorate %fragColor Location 0
+OpDecorate %texture0 DescriptorSet 0
+OpDecorate %texture0 Binding 1
+OpDecorate %sampler0 DescriptorSet 0
+OpDecorate %sampler0 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_2 = OpConstant %float 2
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoord = OpVariable %_ptr_Input_v2float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_1 = OpConstant %float 1
+%uint_1 = OpConstant %uint 1
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%fragColor = OpVariable %_ptr_Output_v4float Output
+%23 = OpTypeImage %float Cube 0 0 0 1 Unknown
+%_ptr_UniformConstant_23 = OpTypePointer UniformConstant %23
+%texture0 = OpVariable %_ptr_UniformConstant_23 UniformConstant
+%25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%sampler0 = OpVariable %_ptr_UniformConstant_25 UniformConstant
+%27 = OpTypeSampledImage %23
+%v3float = OpTypeVector %float 3
+%int = OpTypeInt 32 1
+%main = OpFunction %void None %10
+%29 = OpLabel
+%sc = OpVariable %_ptr_Function_float Function
+%tc = OpVariable %_ptr_Function_float Function
+%30 = OpAccessChain %_ptr_Input_float %texCoord %uint_0
+%31 = OpLoad %float %30
+%32 = OpFMul %float %float_2 %31
+%33 = OpFSub %float %32 %float_1
+OpStore %sc %33
+%34 = OpAccessChain %_ptr_Input_float %texCoord %uint_1
+%35 = OpLoad %float %34
+%36 = OpFMul %float %float_2 %35
+%37 = OpFSub %float %36 %float_1
+OpStore %tc %37
+%38 = OpLoad %23 %texture0
+%39 = OpLoad %25 %sampler0
+%40 = OpSampledImage %27 %38 %39
+%41 = OpLoad %float %tc
+%42 = OpLoad %float %sc
+%43 = OpFNegate %float %42
+%44 = OpCompositeConstruct %v3float %float_1 %41 %43
+%45 = OpImageSampleImplicitLod %v4float %40 %44
+OpStore %fragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<GraphicsRobustAccessPass>(shader, expected, false,
+                                                  true);
+}
+
 // 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/if_conversion_test.cpp b/test/opt/if_conversion_test.cpp
index aa5adea..dee15c3 100644
--- a/test/opt/if_conversion_test.cpp
+++ b/test/opt/if_conversion_test.cpp
@@ -504,6 +504,59 @@
   SinglePassRunAndCheck<IfConversion>(text, text, true, true);
 }
 
+TEST_F(IfConversionTest, DebugInfoSimpleIfThenElse) {
+  // When it replaces an OpPhi with OpSelect, the new OpSelect must have
+  // the same scope and line information with the OpPhi.
+  const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: DebugScope
+; CHECK-NEXT: OpLine {{%\w+}} 3 7
+; CHECK-NEXT: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK-NEXT: DebugValue {{%\w+}} [[sel]]
+; CHECK: OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+%ext = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%name = OpString "test"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_32 = OpConstant %uint 32
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%null_expr = OpExtInst %void %ext DebugExpression
+%src = OpExtInst %void %ext DebugSource %name
+%cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL
+%dbg_tf = OpExtInst %void %ext DebugTypeBasic %name %uint_32 Float
+%dbg_f = OpExtInst %void %ext DebugLocalVariable %name %dbg_tf %src 0 0 %cu FlagIsLocal
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %16
+%15 = OpLabel
+OpBranch %14
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%scope = OpExtInst %void %ext DebugScope %cu
+OpLine %name 3 7
+%18 = OpPhi %uint %uint_0 %15 %uint_1 %16
+%value = OpExtInst %void %ext DebugValue %dbg_f %18 %null_expr
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/inline_opaque_test.cpp b/test/opt/inline_opaque_test.cpp
index b8d2dfa..47e0533 100644
--- a/test/opt/inline_opaque_test.cpp
+++ b/test/opt/inline_opaque_test.cpp
@@ -90,7 +90,8 @@
 )";
 
   const std::string after =
-      R"(%main = OpFunction %void None %12
+      R"(%34 = OpUndef %void
+%main = OpFunction %void None %12
 %28 = OpLabel
 %s0 = OpVariable %_ptr_Function_S_t Function
 %param = OpVariable %_ptr_Function_S_t Function
@@ -289,7 +290,8 @@
 )";
 
   const std::string after =
-      R"(%main2 = OpFunction %void None %13
+      R"(%35 = OpUndef %void
+%main2 = OpFunction %void None %13
 %29 = OpLabel
 %s0 = OpVariable %_ptr_Function_S_t Function
 %param = OpVariable %_ptr_Function_S_t Function
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index ffd3e38..c0ca6da 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -381,6 +381,7 @@
 
   const std::vector<const char*> after = {
       // clang-format off
+         "%26 = OpUndef %void",
        "%main = OpFunction %void None %11",
          "%23 = OpLabel",
           "%b = OpVariable %_ptr_Function_v4float Function",
@@ -1503,11 +1504,11 @@
 %bool = OpTypeBool
 %true = OpConstantTrue %bool
 %void = OpTypeVoid
+%5 = OpTypeFunction %void
 )";
 
   const std::string nonEntryFuncs =
-      R"(%5 = OpTypeFunction %void
-%6 = OpFunction %void None %5
+      R"(%6 = OpFunction %void None %5
 %7 = OpLabel
 OpBranch %8
 %8 = OpLabel
@@ -1542,9 +1543,11 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
-                                              predefs + nonEntryFuncs + after,
-                                              false, true);
+  const std::string undef = "%11 = OpUndef %void\n";
+
+  SinglePassRunAndCheck<InlineExhaustivePass>(
+      predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after,
+      false, true);
 }
 
 TEST_F(InlineTest, MultiBlockLoopHeaderCallsMultiBlockCallee) {
@@ -1619,9 +1622,10 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
-                                              predefs + nonEntryFuncs + after,
-                                              false, true);
+  const std::string undef = "%20 = OpUndef %void\n";
+  SinglePassRunAndCheck<InlineExhaustivePass>(
+      predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after,
+      false, true);
 }
 
 TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge) {
@@ -1635,7 +1639,7 @@
   // the OpSelectionMerge, so inlining must create a new block to contain
   // the callee contents.
   //
-  // Additionally, we have two dummy OpCopyObject instructions to prove that
+  // Additionally, we have two extra OpCopyObject instructions to prove that
   // the OpLoopMerge is moved to the right location.
   //
   // Also ensure that OpPhis within the cloned callee code are valid.
@@ -1707,10 +1711,10 @@
 OpReturn
 OpFunctionEnd
 )";
-
-  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
-                                              predefs + nonEntryFuncs + after,
-                                              false, true);
+  const std::string undef = "%15 = OpUndef %void\n";
+  SinglePassRunAndCheck<InlineExhaustivePass>(
+      predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after,
+      false, true);
 }
 
 TEST_F(InlineTest,
@@ -1789,9 +1793,10 @@
 OpFunctionEnd
 )";
 
-  SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
-                                              predefs + nonEntryFuncs + after,
-                                              false, true);
+  const std::string undef = "%20 = OpUndef %void\n";
+  SinglePassRunAndCheck<InlineExhaustivePass>(
+      predefs + nonEntryFuncs + before, predefs + undef + nonEntryFuncs + after,
+      false, true);
 }
 
 TEST_F(InlineTest, NonInlinableCalleeWithSingleReturn) {
@@ -2164,6 +2169,7 @@
 OpName %foo_entry "foo_entry"
 %void = OpTypeVoid
 %void_fn = OpTypeFunction %void
+%3 = OpUndef %void
 %foo = OpFunction %void None %void_fn
 %foo_entry = OpLabel
 OpReturn
@@ -2399,6 +2405,38 @@
   SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
 }
 
+TEST_F(InlineTest, DontInlineFuncWithDontInline) {
+  // Check that the function with DontInline flag is not inlined.
+  const std::string text = R"(
+; CHECK: %foo = OpFunction %int DontInline
+; CHECK: OpReturnValue %int_0
+
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %main "main"
+OpName %foo "foo"
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%7 = OpTypeFunction %int
+%main = OpFunction %void None %6
+%8 = OpLabel
+%9 = OpFunctionCall %int %foo
+OpReturn
+OpFunctionEnd
+%foo = OpFunction %int DontInline %7
+%10 = OpLabel
+OpReturnValue %int_0
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
 TEST_F(InlineTest, InlineFuncWithOpKillNotInContinue) {
   const std::string before =
       R"(OpCapability Shader
@@ -2437,6 +2475,7 @@
 %3 = OpTypeFunction %void
 %bool = OpTypeBool
 %true = OpConstantTrue %bool
+%16 = OpUndef %void
 %main = OpFunction %void None %3
 %5 = OpLabel
 OpKill
@@ -2534,6 +2573,7 @@
 %3 = OpTypeFunction %void
 %bool = OpTypeBool
 %true = OpConstantTrue %bool
+%16 = OpUndef %void
 %main = OpFunction %void None %3
 %5 = OpLabel
 OpTerminateInvocation
@@ -2761,6 +2801,7 @@
 %uint_0 = OpConstant %uint 0
 %false = OpConstantFalse %bool
 %_ptr_Function_bool = OpTypePointer Function %bool
+%11 = OpUndef %void
 %foo_ = OpFunction %void None %4
 %7 = OpLabel
 %18 = OpVariable %_ptr_Function_bool Function %false
@@ -3849,6 +3890,35 @@
   SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
 }
 
+TEST_F(InlineTest, UsingVoidFunctionResult) {
+  const std::string text = R"(
+; CHECK: [[undef:%\w+]] = OpUndef %void
+; CHECK: OpFunction
+; CHECK: OpCopyObject %void [[undef]]
+; CHECK: OpFunctionEnd
+               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
+          %8 = OpFunctionCall %2 %6
+          %9 = OpCopyObject %2 %8
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  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 d867b01..67a4968 100644
--- a/test/opt/inst_bindless_check_test.cpp
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -2103,7 +2103,7 @@
 %uint_7 = OpConstant %uint 7
 %uint_8 = OpConstant %uint 8
 %uint_9 = OpConstant %uint 9
-%uint_93 = OpConstant %uint 93
+%uint_109 = OpConstant %uint 109
 %125 = OpConstantNull %v4float
 )";
 
@@ -2180,19 +2180,23 @@
 %54 = OpSampledImage %37 %52 %53
 %55 = OpAccessChain %_ptr_Function_v2float %i %int_0
 %56 = OpLoad %v2float %55
+OpNoLine
 %62 = OpULessThan %bool %50 %uint_128
 OpSelectionMerge %63 None
 OpBranchConditional %62 %64 %65
 %64 = OpLabel
 %66 = OpLoad %27 %51
 %67 = OpSampledImage %37 %66 %53
+OpLine %5 24 0
 %68 = OpImageSampleImplicitLod %v4float %67 %56
+OpNoLine
 OpBranch %63
 %65 = OpLabel
-%124 = OpFunctionCall %void %69 %uint_93 %uint_0 %50 %uint_128
+%124 = OpFunctionCall %void %69 %uint_109 %uint_0 %50 %uint_128
 OpBranch %63
 %63 = OpLabel
 %126 = OpPhi %v4float %68 %64 %125 %65
+OpLine %5 24 0
 %58 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
 OpStore %58 %126
 OpLine %5 25 0
@@ -2444,7 +2448,7 @@
 %61 = OpLoad %16 %33
 %62 = OpSampledImage %26 %61 %35
 %136 = OpFunctionCall %uint %118 %uint_0 %uint_1 %uint_2 %32
-%137 = OpINotEqual %bool %136 %uint_0
+%137 = OpULessThan %bool %uint_0 %136
 OpSelectionMerge %138 None
 OpBranchConditional %137 %139 %140
 %139 = OpLabel
@@ -2697,7 +2701,7 @@
 %22 = OpLoad %14 %g_sAniso
 %23 = OpSampledImage %16 %21 %22
 %50 = OpFunctionCall %uint %27 %uint_0 %uint_0 %uint_0 %uint_0
-%52 = OpINotEqual %bool %50 %uint_0
+%52 = OpULessThan %bool %uint_0 %50
 OpSelectionMerge %54 None
 OpBranchConditional %52 %55 %56
 %55 = OpLabel
@@ -3075,7 +3079,7 @@
 %44 = OpLabel
 %103 = OpBitcast %uint %7
 %122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103
-%123 = OpINotEqual %bool %122 %uint_0
+%123 = OpULessThan %bool %uint_0 %122
 OpSelectionMerge %124 None
 OpBranchConditional %123 %125 %126
 %125 = OpLabel
@@ -3356,7 +3360,7 @@
 %44 = OpLabel
 %103 = OpBitcast %uint %7
 %122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103
-%123 = OpINotEqual %bool %122 %uint_0
+%123 = OpULessThan %bool %uint_0 %122
 OpSelectionMerge %124 None
 OpBranchConditional %123 %125 %126
 %125 = OpLabel
@@ -3626,7 +3630,7 @@
 %44 = OpLabel
 %103 = OpBitcast %uint %7
 %122 = OpFunctionCall %uint %104 %uint_0 %uint_0 %uint_3 %103
-%123 = OpINotEqual %bool %122 %uint_0
+%123 = OpULessThan %bool %uint_0 %122
 OpSelectionMerge %124 None
 OpBranchConditional %123 %125 %126
 %125 = OpLabel
@@ -3870,7 +3874,7 @@
 %14 = OpLabel
 %15 = OpAccessChain %_ptr_Uniform_float %uniformBuffer %int_0
 %43 = OpFunctionCall %uint %20 %uint_0 %uint_0 %uint_3 %uint_0
-%45 = OpINotEqual %bool %43 %uint_0
+%45 = OpULessThan %bool %uint_0 %43
 OpSelectionMerge %47 None
 OpBranchConditional %45 %48 %49
 %48 = OpLabel
@@ -4128,7 +4132,7 @@
 %44 = OpLabel
 %100 = OpBitcast %uint %7
 %119 = OpFunctionCall %uint %101 %uint_0 %uint_0 %uint_4 %100
-%120 = OpINotEqual %bool %119 %uint_0
+%120 = OpULessThan %bool %uint_0 %119
 OpSelectionMerge %121 None
 OpBranchConditional %120 %122 %123
 %122 = OpLabel
@@ -4405,7 +4409,7 @@
 %27 = OpLabel
 %90 = OpBitcast %uint %7
 %112 = OpFunctionCall %uint %91 %uint_0 %uint_0 %uint_3 %90
-%113 = OpINotEqual %bool %112 %uint_0
+%113 = OpULessThan %bool %uint_0 %112
 OpSelectionMerge %114 None
 OpBranchConditional %113 %115 %116
 %115 = OpLabel
@@ -4682,7 +4686,7 @@
 %24 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %132 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_0 %uint_0
-%133 = OpINotEqual %bool %132 %uint_0
+%133 = OpULessThan %bool %uint_0 %132
 OpSelectionMerge %134 None
 OpBranchConditional %133 %135 %136
 %135 = OpLabel
@@ -4702,7 +4706,7 @@
 %52 = OpLabel
 %54 = OpLoad %13 %27
 %142 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_1 %141
-%143 = OpINotEqual %bool %142 %uint_0
+%143 = OpULessThan %bool %uint_0 %142
 OpSelectionMerge %144 None
 OpBranchConditional %143 %145 %146
 %145 = OpLabel
@@ -4723,7 +4727,7 @@
 %30 = OpCompositeExtract %float %113 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 %151 = OpFunctionCall %uint %114 %uint_0 %uint_0 %uint_0 %uint_0
-%152 = OpINotEqual %bool %151 %uint_0
+%152 = OpULessThan %bool %uint_0 %151
 OpSelectionMerge %153 None
 OpBranchConditional %152 %154 %155
 %154 = OpLabel
@@ -5006,7 +5010,7 @@
 %24 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpINotEqual %bool %133 %uint_0
+%134 = OpULessThan %bool %uint_0 %133
 OpSelectionMerge %135 None
 OpBranchConditional %134 %136 %137
 %136 = OpLabel
@@ -5026,7 +5030,7 @@
 %52 = OpLabel
 %54 = OpLoad %13 %27
 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpINotEqual %bool %143 %uint_0
+%144 = OpULessThan %bool %uint_0 %143
 OpSelectionMerge %145 None
 OpBranchConditional %144 %146 %147
 %146 = OpLabel
@@ -5047,7 +5051,7 @@
 %30 = OpCompositeExtract %float %114 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpINotEqual %bool %152 %uint_0
+%153 = OpULessThan %bool %uint_0 %152
 OpSelectionMerge %154 None
 OpBranchConditional %153 %155 %156
 %155 = OpLabel
@@ -5330,7 +5334,7 @@
 %24 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpINotEqual %bool %133 %uint_0
+%134 = OpULessThan %bool %uint_0 %133
 OpSelectionMerge %135 None
 OpBranchConditional %134 %136 %137
 %136 = OpLabel
@@ -5350,7 +5354,7 @@
 %52 = OpLabel
 %54 = OpLoad %13 %27
 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpINotEqual %bool %143 %uint_0
+%144 = OpULessThan %bool %uint_0 %143
 OpSelectionMerge %145 None
 OpBranchConditional %144 %146 %147
 %146 = OpLabel
@@ -5371,7 +5375,7 @@
 %30 = OpCompositeExtract %float %114 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpINotEqual %bool %152 %uint_0
+%153 = OpULessThan %bool %uint_0 %152
 OpSelectionMerge %154 None
 OpBranchConditional %153 %155 %156
 %155 = OpLabel
@@ -5654,7 +5658,7 @@
 %24 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpINotEqual %bool %133 %uint_0
+%134 = OpULessThan %bool %uint_0 %133
 OpSelectionMerge %135 None
 OpBranchConditional %134 %136 %137
 %136 = OpLabel
@@ -5674,7 +5678,7 @@
 %52 = OpLabel
 %54 = OpLoad %13 %27
 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpINotEqual %bool %143 %uint_0
+%144 = OpULessThan %bool %uint_0 %143
 OpSelectionMerge %145 None
 OpBranchConditional %144 %146 %147
 %146 = OpLabel
@@ -5695,7 +5699,7 @@
 %30 = OpCompositeExtract %float %114 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpINotEqual %bool %152 %uint_0
+%153 = OpULessThan %bool %uint_0 %152
 OpSelectionMerge %154 None
 OpBranchConditional %153 %155 %156
 %155 = OpLabel
@@ -5978,7 +5982,7 @@
 %24 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpINotEqual %bool %133 %uint_0
+%134 = OpULessThan %bool %uint_0 %133
 OpSelectionMerge %135 None
 OpBranchConditional %134 %136 %137
 %136 = OpLabel
@@ -5998,7 +6002,7 @@
 %52 = OpLabel
 %54 = OpLoad %13 %27
 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpINotEqual %bool %143 %uint_0
+%144 = OpULessThan %bool %uint_0 %143
 OpSelectionMerge %145 None
 OpBranchConditional %144 %146 %147
 %146 = OpLabel
@@ -6019,7 +6023,7 @@
 %30 = OpCompositeExtract %float %114 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpINotEqual %bool %152 %uint_0
+%153 = OpULessThan %bool %uint_0 %152
 OpSelectionMerge %154 None
 OpBranchConditional %153 %155 %156
 %155 = OpLabel
@@ -6302,7 +6306,7 @@
 %24 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpINotEqual %bool %133 %uint_0
+%134 = OpULessThan %bool %uint_0 %133
 OpSelectionMerge %135 None
 OpBranchConditional %134 %136 %137
 %136 = OpLabel
@@ -6322,7 +6326,7 @@
 %52 = OpLabel
 %54 = OpLoad %13 %27
 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpINotEqual %bool %143 %uint_0
+%144 = OpULessThan %bool %uint_0 %143
 OpSelectionMerge %145 None
 OpBranchConditional %144 %146 %147
 %146 = OpLabel
@@ -6343,7 +6347,7 @@
 %30 = OpCompositeExtract %float %114 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpINotEqual %bool %152 %uint_0
+%153 = OpULessThan %bool %uint_0 %152
 OpSelectionMerge %154 None
 OpBranchConditional %153 %155 %156
 %155 = OpLabel
@@ -6626,7 +6630,7 @@
 %24 = OpLabel
 %25 = OpAccessChain %_ptr_Uniform_uint %sbo %int_0
 %133 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%134 = OpINotEqual %bool %133 %uint_0
+%134 = OpULessThan %bool %uint_0 %133
 OpSelectionMerge %135 None
 OpBranchConditional %134 %136 %137
 %136 = OpLabel
@@ -6646,7 +6650,7 @@
 %52 = OpLabel
 %54 = OpLoad %13 %27
 %143 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_1 %142
-%144 = OpINotEqual %bool %143 %uint_0
+%144 = OpULessThan %bool %uint_0 %143
 OpSelectionMerge %145 None
 OpBranchConditional %144 %146 %147
 %146 = OpLabel
@@ -6667,7 +6671,7 @@
 %30 = OpCompositeExtract %float %114 0
 %31 = OpAccessChain %_ptr_Uniform_float %sbo %int_1
 %152 = OpFunctionCall %uint %115 %uint_0 %uint_0 %uint_0 %uint_0
-%153 = OpINotEqual %bool %152 %uint_0
+%153 = OpULessThan %bool %uint_0 %152
 OpSelectionMerge %154 None
 OpBranchConditional %153 %155 %156
 %155 = OpLabel
@@ -7038,7 +7042,7 @@
 %64 = OpSampledImage %27 %63 %26
 %124 = OpBitcast %uint %19
 %146 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_3 %124
-%147 = OpINotEqual %bool %146 %uint_0
+%147 = OpULessThan %bool %uint_0 %146
 OpSelectionMerge %148 None
 OpBranchConditional %147 %149 %150
 %149 = OpLabel
@@ -7067,7 +7071,7 @@
 %42 = OpLoad %v2float %inTexcoord
 %47 = OpAccessChain %_ptr_Uniform_v2float %uniforms %int_0
 %157 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_0 %uint_0
-%158 = OpINotEqual %bool %157 %uint_0
+%158 = OpULessThan %bool %uint_0 %157
 OpSelectionMerge %159 None
 OpBranchConditional %158 %160 %161
 %160 = OpLabel
@@ -7081,7 +7085,7 @@
 %49 = OpFMul %v2float %42 %166
 %167 = OpSampledImage %27 %39 %40
 %168 = OpFunctionCall %uint %125 %uint_0 %uint_0 %uint_2 %uint_0
-%169 = OpINotEqual %bool %168 %uint_0
+%169 = OpULessThan %bool %uint_0 %168
 OpSelectionMerge %170 None
 OpBranchConditional %169 %171 %172
 %171 = OpLabel
@@ -7181,6 +7185,1295 @@
       true, 7u, 23u, true, true);
 }
 
+TEST_F(InstBindlessTest, MultipleUniformNonAggregateRefsNoDescInit) {
+  // Check that uniform refs do not go out-of-bounds. All checks use same input
+  // buffer read function call result at top of function for uniform buffer
+  // length. Because descriptor indexing is not being checked, we can avoid one
+  // buffer load.
+  //
+  // Texture2D g_tColor;
+  // SamplerState g_sAniso;
+  //
+  // layout(push_constant) cbuffer PerViewPushConst_t { bool g_B; };
+  //
+  // cbuffer PerViewConstantBuffer_t {
+  //   float2 g_TexOff0;
+  //   float2 g_TexOff1;
+  // };
+  //
+  // struct PS_INPUT {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i) {
+  //   PS_OUTPUT ps_output;
+  //   float2 off;
+  //   float2 vtc;
+  //   if (g_B)
+  //     off = g_TexOff0;
+  //   else
+  //     off = g_TexOff1;
+  //   vtc = i.vTextureCoords.xy + off;
+  //   ps_output.vColor = g_tColor.Sample(g_sAniso, vtc);
+  //   return ps_output;
+  // }
+
+  const std::string text = R"(
+               OpCapability Shader
+;CHECK:        OpExtension "SPV_KHR_storage_buffer_storage_class"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
+;CHECK:        OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %130 %157 %gl_FragCoord
+               OpExecutionMode %MainPs OriginUpperLeft
+               OpSource HLSL 500
+               OpName %MainPs "MainPs"
+               OpName %PerViewPushConst_t "PerViewPushConst_t"
+               OpMemberName %PerViewPushConst_t 0 "g_B"
+               OpName %_ ""
+               OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+               OpMemberName %PerViewConstantBuffer_t 0 "g_TexOff0"
+               OpMemberName %PerViewConstantBuffer_t 1 "g_TexOff1"
+               OpName %__0 ""
+               OpName %g_tColor "g_tColor"
+               OpName %g_sAniso "g_sAniso"
+               OpName %i_vTextureCoords "i.vTextureCoords"
+               OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+               OpMemberDecorate %PerViewPushConst_t 0 Offset 0
+               OpDecorate %PerViewPushConst_t Block
+               OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+               OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 8
+               OpDecorate %PerViewConstantBuffer_t Block
+               OpDecorate %__0 DescriptorSet 0
+               OpDecorate %__0 Binding 1
+               OpDecorate %g_tColor DescriptorSet 0
+               OpDecorate %g_tColor Binding 0
+               OpDecorate %g_sAniso DescriptorSet 0
+               OpDecorate %g_sAniso Binding 2
+               OpDecorate %i_vTextureCoords Location 0
+               OpDecorate %_entryPointOutput_vColor Location 0
+ ;CHECK:       OpDecorate %_runtimearr_uint ArrayStride 4
+ ;CHECK:       OpDecorate %_struct_128 Block
+ ;CHECK:       OpMemberDecorate %_struct_128 0 Offset 0
+ ;CHECK:       OpDecorate %130 DescriptorSet 7
+ ;CHECK:       OpDecorate %130 Binding 1
+ ;CHECK:       OpDecorate %_struct_155 Block
+ ;CHECK:       OpMemberDecorate %_struct_155 0 Offset 0
+ ;CHECK:       OpMemberDecorate %_struct_155 1 Offset 4
+ ;CHECK:       OpDecorate %157 DescriptorSet 7
+ ;CHECK:       OpDecorate %157 Binding 0
+ ;CHECK:       OpDecorate %gl_FragCoord BuiltIn FragCoord
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+    %v4float = OpTypeVector %float 4
+       %uint = OpTypeInt 32 0
+%PerViewPushConst_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewPushConst_t = OpTypePointer PushConstant %PerViewPushConst_t
+          %_ = OpVariable %_ptr_PushConstant_PerViewPushConst_t PushConstant
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+       %bool = OpTypeBool
+     %uint_0 = OpConstant %uint 0
+%PerViewConstantBuffer_t = OpTypeStruct %v2float %v2float
+%_ptr_Uniform_PerViewConstantBuffer_t = OpTypePointer Uniform %PerViewConstantBuffer_t
+        %__0 = OpVariable %_ptr_Uniform_PerViewConstantBuffer_t Uniform
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+      %int_1 = OpConstant %int 1
+         %49 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_49 = OpTypePointer UniformConstant %49
+   %g_tColor = OpVariable %_ptr_UniformConstant_49 UniformConstant
+         %53 = OpTypeSampler
+%_ptr_UniformConstant_53 = OpTypePointer UniformConstant %53
+   %g_sAniso = OpVariable %_ptr_UniformConstant_53 UniformConstant
+         %57 = OpTypeSampledImage %49
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+ ;CHECK:      %uint_7 = OpConstant %uint 7
+ ;CHECK:      %uint_1 = OpConstant %uint 1
+ ;CHECK:         %122 = OpTypeFunction %uint %uint %uint %uint
+ ;CHECK: %_runtimearr_uint = OpTypeRuntimeArray %uint
+ ;CHECK: %_struct_128 = OpTypeStruct %_runtimearr_uint
+ ;CHECK: %_ptr_StorageBuffer__struct_128 = OpTypePointer StorageBuffer %_struct_128
+ ;CHECK:         %130 = OpVariable %_ptr_StorageBuffer__struct_128 StorageBuffer
+ ;CHECK: %_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+ ;CHECK:      %uint_3 = OpConstant %uint 3
+ ;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:%_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_8 = OpConstant %uint 8
+ ;CHECK:     %uint_9 = OpConstant %uint 9
+ ;CHECK:    %uint_10 = OpConstant %uint 10
+ ;CHECK:    %uint_71 = OpConstant %uint 71
+ ;CHECK:        %202 = OpConstantNull %v2float
+ ;CHECK:    %uint_75 = OpConstant %uint 75
+     %MainPs = OpFunction %void None %3
+          %5 = OpLabel
+ ;CHECK: %140 = OpFunctionCall %uint %121 %uint_1 %uint_1 %uint_0
+ ;CHECK:        OpBranch %117
+ ;CHECK: %117 = OpLabel
+ ;CHECK:        OpBranch %116
+ ;CHECK: %116 = OpLabel
+         %69 = OpLoad %v2float %i_vTextureCoords
+         %82 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+         %83 = OpLoad %uint %82
+         %84 = OpINotEqual %bool %83 %uint_0
+               OpSelectionMerge %91 None
+               OpBranchConditional %84 %85 %88
+         %85 = OpLabel
+         %86 = OpAccessChain %_ptr_Uniform_v2float %__0 %int_0
+         %87 = OpLoad %v2float %86
+ ;CHECK-NOT:     %87 = OpLoad %v2float %86
+ ;CHECK:        %119 = OpIAdd %uint %uint_0 %uint_7
+ ;CHECK:        %141 = OpULessThan %bool %119 %140
+ ;CHECK:               OpSelectionMerge %143 None
+ ;CHECK:               OpBranchConditional %141 %144 %145
+ ;CHECK:        %144 = OpLabel
+ ;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:               OpBranch %143
+ ;CHECK:        %143 = OpLabel
+ ;CHECK:        %203 = OpPhi %v2float %146 %144 %202 %145
+               OpBranch %91
+         %88 = OpLabel
+         %89 = OpAccessChain %_ptr_Uniform_v2float %__0 %int_1
+         %90 = OpLoad %v2float %89
+ ;CHECK-NOT:     %90 = OpLoad %v2float %89
+ ;CHECK:        %204 = OpIAdd %uint %uint_8 %uint_7
+ ;CHECK:        %205 = OpULessThan %bool %204 %140
+ ;CHECK:               OpSelectionMerge %206 None
+ ;CHECK:               OpBranchConditional %205 %207 %208
+ ;CHECK:        %207 = OpLabel
+ ;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:               OpBranch %206
+ ;CHECK:        %206 = OpLabel
+ ;CHECK:        %212 = OpPhi %v2float %209 %207 %202 %208
+               OpBranch %91
+         %91 = OpLabel
+        %115 = OpPhi %v2float %87 %85 %90 %88
+ ;CHECK-NOT:       %115 = OpPhi %v2float %87 %85 %90 %88
+ ;CHECK:           %115 = OpPhi %v2float %203 %143 %212 %206
+         %95 = OpFAdd %v2float %69 %115
+         %96 = OpLoad %49 %g_tColor
+         %97 = OpLoad %53 %g_sAniso
+         %98 = OpSampledImage %57 %96 %97
+        %100 = OpImageSampleImplicitLod %v4float %98 %95
+               OpStore %_entryPointOutput_vColor %100
+               OpReturn
+               OpFunctionEnd
+ ;CHECK:        %121 = OpFunction %uint None %122
+ ;CHECK:        %123 = OpFunctionParameter %uint
+ ;CHECK:        %124 = OpFunctionParameter %uint
+ ;CHECK:        %125 = OpFunctionParameter %uint
+ ;CHECK:        %126 = OpLabel
+ ;CHECK:        %132 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %123
+ ;CHECK:        %133 = OpLoad %uint %132
+ ;CHECK:        %134 = OpIAdd %uint %133 %124
+ ;CHECK:        %135 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %134
+ ;CHECK:        %136 = OpLoad %uint %135
+ ;CHECK:        %137 = OpIAdd %uint %136 %125
+ ;CHECK:        %138 = OpAccessChain %_ptr_StorageBuffer_uint %130 %uint_0 %137
+ ;CHECK:        %139 = OpLoad %uint %138
+ ;CHECK:               OpReturnValue %139
+ ;CHECK:               OpFunctionEnd
+ ;CHECK:        %147 = OpFunction %void None %148
+ ;CHECK:        %149 = OpFunctionParameter %uint
+ ;CHECK:        %150 = OpFunctionParameter %uint
+ ;CHECK:        %151 = OpFunctionParameter %uint
+ ;CHECK:        %152 = OpFunctionParameter %uint
+ ;CHECK:        %153 = OpFunctionParameter %uint
+ ;CHECK:        %154 = OpLabel
+ ;CHECK:        %158 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_0
+ ;CHECK:        %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:        %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:        %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:        %188 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %187
+ ;CHECK:               OpStore %188 %185
+ ;CHECK:        %189 = OpIAdd %uint %161 %uint_7
+ ;CHECK:        %190 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %189
+ ;CHECK:               OpStore %190 %150
+ ;CHECK:        %192 = OpIAdd %uint %161 %uint_8
+ ;CHECK:        %193 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %192
+ ;CHECK:               OpStore %193 %151
+ ;CHECK:        %195 = OpIAdd %uint %161 %uint_9
+ ;CHECK:        %196 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %195
+ ;CHECK:               OpStore %196 %152
+ ;CHECK:        %198 = OpIAdd %uint %161 %uint_10
+ ;CHECK:        %199 = OpAccessChain %_ptr_StorageBuffer_uint %157 %uint_1 %198
+ ;CHECK:               OpStore %199 %153
+ ;CHECK:               OpBranch %165
+ ;CHECK:        %165 = OpLabel
+ ;CHECK:               OpReturn
+ ;CHECK:               OpFunctionEnd
+ )";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false,
+                                               false, true);
+}
+
+TEST_F(InstBindlessTest, UniformArrayRefNoDescInit) {
+  // Check that uniform array ref does not go out-of-bounds.
+  //
+  // Texture2D g_tColor;
+  // SamplerState g_sAniso;
+  //
+  // layout(push_constant) cbuffer PerViewPushConst_t { uint g_c; };
+  //
+  // struct PerBatchEnvMapConstantBuffer_t {
+  //   float4x3 g_matEnvMapWorldToLocal;
+  //   float4 g_vEnvironmentMapBoxMins;
+  //   float2 g_TexOff;
+  // };
+  //
+  // cbuffer _BindlessFastEnvMapCB_PS_t {
+  //   PerBatchEnvMapConstantBuffer_t g_envMapConstants[128];
+  // };
+  //
+  // struct PS_INPUT {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i) {
+  //   PS_OUTPUT ps_output;
+  //   float2 off;
+  //   float2 vtc;
+  //   off = g_envMapConstants[g_c].g_TexOff;
+  //   vtc = i.vTextureCoords.xy + off;
+  //   ps_output.vColor = g_tColor.Sample(g_sAniso, vtc);
+  //   return ps_output;
+  // }
+
+  const std::string text = R"(
+               OpCapability Shader
+;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
+               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"
+               OpMemberName %_BindlessFastEnvMapCB_PS_t 0 "g_envMapConstants"
+               OpName %_ ""
+               OpName %PerViewPushConst_t "PerViewPushConst_t"
+               OpMemberName %PerViewPushConst_t 0 "g_c"
+               OpName %__0 ""
+               OpName %g_tColor "g_tColor"
+               OpName %g_sAniso "g_sAniso"
+               OpName %i_vTextureCoords "i.vTextureCoords"
+               OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 RowMajor
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 Offset 0
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 MatrixStride 16
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 1 Offset 48
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 2 Offset 64
+               OpDecorate %_arr_PerBatchEnvMapConstantBuffer_t_uint_128 ArrayStride 80
+               OpMemberDecorate %_BindlessFastEnvMapCB_PS_t 0 Offset 0
+               OpDecorate %_BindlessFastEnvMapCB_PS_t Block
+               OpDecorate %_ DescriptorSet 0
+               OpDecorate %_ Binding 2
+               OpMemberDecorate %PerViewPushConst_t 0 Offset 0
+               OpDecorate %PerViewPushConst_t Block
+               OpDecorate %g_tColor DescriptorSet 0
+               OpDecorate %g_tColor Binding 0
+               OpDecorate %g_sAniso DescriptorSet 0
+               OpDecorate %g_sAniso Binding 1
+               OpDecorate %i_vTextureCoords Location 0
+               OpDecorate %_entryPointOutput_vColor Location 0
+;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
+;CHECK:               OpDecorate %_struct_111 Block
+;CHECK:               OpMemberDecorate %_struct_111 0 Offset 0
+;CHECK:               OpDecorate %113 DescriptorSet 7
+;CHECK:               OpDecorate %113 Binding 1
+;CHECK:               OpDecorate %_struct_139 Block
+;CHECK:               OpMemberDecorate %_struct_139 0 Offset 0
+;CHECK:               OpMemberDecorate %_struct_139 1 Offset 4
+;CHECK:               OpDecorate %141 DescriptorSet 7
+;CHECK:               OpDecorate %141 Binding 0
+;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+    %v4float = OpTypeVector %float 4
+    %v3float = OpTypeVector %float 3
+%mat4v3float = OpTypeMatrix %v3float 4
+%PerBatchEnvMapConstantBuffer_t = OpTypeStruct %mat4v3float %v4float %v2float
+       %uint = OpTypeInt 32 0
+   %uint_128 = OpConstant %uint 128
+%_arr_PerBatchEnvMapConstantBuffer_t_uint_128 = OpTypeArray %PerBatchEnvMapConstantBuffer_t %uint_128
+%_BindlessFastEnvMapCB_PS_t = OpTypeStruct %_arr_PerBatchEnvMapConstantBuffer_t_uint_128
+%_ptr_Uniform__BindlessFastEnvMapCB_PS_t = OpTypePointer Uniform %_BindlessFastEnvMapCB_PS_t
+          %_ = OpVariable %_ptr_Uniform__BindlessFastEnvMapCB_PS_t Uniform
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+%PerViewPushConst_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewPushConst_t = OpTypePointer PushConstant %PerViewPushConst_t
+        %__0 = OpVariable %_ptr_PushConstant_PerViewPushConst_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+      %int_2 = OpConstant %int 2
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+         %46 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_46 = OpTypePointer UniformConstant %46
+   %g_tColor = OpVariable %_ptr_UniformConstant_46 UniformConstant
+         %50 = OpTypeSampler
+%_ptr_UniformConstant_50 = OpTypePointer UniformConstant %50
+   %g_sAniso = OpVariable %_ptr_UniformConstant_50 UniformConstant
+         %54 = OpTypeSampledImage %46
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+;CHECK:     %uint_0 = OpConstant %uint 0
+;CHECK:    %uint_80 = OpConstant %uint 80
+;CHECK:    %uint_64 = OpConstant %uint 64
+;CHECK:     %uint_7 = OpConstant %uint 7
+;CHECK:     %uint_2 = OpConstant %uint 2
+;CHECK:     %uint_1 = OpConstant %uint 1
+;CHECK:        %105 = OpTypeFunction %uint %uint %uint %uint
+;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
+;CHECK:%_struct_111 = OpTypeStruct %_runtimearr_uint
+;CHECK:%_ptr_StorageBuffer__struct_111 = OpTypePointer StorageBuffer %_struct_111
+;CHECK:        %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer
+;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+;CHECK:       %bool = OpTypeBool
+;CHECK:     %uint_3 = OpConstant %uint 3
+;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:%_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_8 = OpConstant %uint 8
+;CHECK:     %uint_9 = OpConstant %uint 9
+;CHECK:    %uint_10 = OpConstant %uint 10
+;CHECK:    %uint_78 = OpConstant %uint 78
+;CHECK:        %185 = OpConstantNull %v2float
+     %MainPs = OpFunction %void None %3
+          %5 = OpLabel
+;CHECK:        %123 = OpFunctionCall %uint %104 %uint_1 %uint_2 %uint_0
+;CHECK:               OpBranch %93
+;CHECK:         %93 = OpLabel
+;CHECK:               OpBranch %92
+;CHECK:         %92 = OpLabel
+         %66 = OpLoad %v2float %i_vTextureCoords
+         %79 = OpAccessChain %_ptr_PushConstant_uint %__0 %int_0
+         %80 = OpLoad %uint %79
+         %81 = OpAccessChain %_ptr_Uniform_v2float %_ %int_0 %80 %int_2
+         %82 = OpLoad %v2float %81
+;CHECK-NOT:     %82 = OpLoad %v2float %81
+;CHECK:         %96 = OpIMul %uint %uint_80 %80
+;CHECK:         %97 = OpIAdd %uint %uint_0 %96
+;CHECK:         %99 = OpIAdd %uint %97 %uint_64
+;CHECK:        %101 = OpIAdd %uint %99 %uint_7
+;CHECK:        %125 = OpULessThan %bool %101 %123
+;CHECK:               OpSelectionMerge %127 None
+;CHECK:               OpBranchConditional %125 %128 %129
+;CHECK:        %128 = OpLabel
+;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:               OpBranch %127
+;CHECK:        %127 = OpLabel
+;CHECK:        %186 = OpPhi %v2float %130 %128 %185 %129
+         %86 = OpFAdd %v2float %66 %82
+;CHECK-NOT:         %86 = OpFAdd %v2float %66 %82
+;CHECK:             %86 = OpFAdd %v2float %66 %186
+         %87 = OpLoad %46 %g_tColor
+         %88 = OpLoad %50 %g_sAniso
+         %89 = OpSampledImage %54 %87 %88
+         %91 = OpImageSampleImplicitLod %v4float %89 %86
+               OpStore %_entryPointOutput_vColor %91
+               OpReturn
+               OpFunctionEnd
+;CHECK:        %104 = OpFunction %uint None %105
+;CHECK:        %106 = OpFunctionParameter %uint
+;CHECK:        %107 = OpFunctionParameter %uint
+;CHECK:        %108 = OpFunctionParameter %uint
+;CHECK:        %109 = OpLabel
+;CHECK:        %115 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %106
+;CHECK:        %116 = OpLoad %uint %115
+;CHECK:        %117 = OpIAdd %uint %116 %107
+;CHECK:        %118 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %117
+;CHECK:        %119 = OpLoad %uint %118
+;CHECK:        %120 = OpIAdd %uint %119 %108
+;CHECK:        %121 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %120
+;CHECK:        %122 = OpLoad %uint %121
+;CHECK:               OpReturnValue %122
+;CHECK:               OpFunctionEnd
+;CHECK:        %131 = OpFunction %void None %132
+;CHECK:        %133 = OpFunctionParameter %uint
+;CHECK:        %134 = OpFunctionParameter %uint
+;CHECK:        %135 = OpFunctionParameter %uint
+;CHECK:        %136 = OpFunctionParameter %uint
+;CHECK:        %137 = OpFunctionParameter %uint
+;CHECK:        %138 = OpLabel
+;CHECK:        %142 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_0
+;CHECK:        %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:        %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:        %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:        %171 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %170
+;CHECK:               OpStore %171 %168
+;CHECK:        %172 = OpIAdd %uint %145 %uint_7
+;CHECK:        %173 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %172
+;CHECK:               OpStore %173 %134
+;CHECK:        %175 = OpIAdd %uint %145 %uint_8
+;CHECK:        %176 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %175
+;CHECK:               OpStore %176 %135
+;CHECK:        %178 = OpIAdd %uint %145 %uint_9
+;CHECK:        %179 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %178
+;CHECK:               OpStore %179 %136
+;CHECK:        %181 = OpIAdd %uint %145 %uint_10
+;CHECK:        %182 = OpAccessChain %_ptr_StorageBuffer_uint %141 %uint_1 %181
+;CHECK:               OpStore %182 %137
+;CHECK:               OpBranch %149
+;CHECK:        %149 = OpLabel
+;CHECK:               OpReturn
+;CHECK:               OpFunctionEnd
+ )";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false,
+                                               false, true);
+}
+
+TEST_F(InstBindlessTest, UniformArrayRefWithDescInit) {
+  // The buffer-oob and desc-init checks should use the same debug
+  // output buffer write function.
+  //
+  // Same source as UniformArrayRefNoDescInit
+
+  const std::string text = R"(
+               OpCapability Shader
+;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
+;CHECK:               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %113 %144 %gl_FragCoord
+               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"
+               OpMemberName %_BindlessFastEnvMapCB_PS_t 0 "g_envMapConstants"
+               OpName %_ ""
+               OpName %PerViewPushConst_t "PerViewPushConst_t"
+               OpMemberName %PerViewPushConst_t 0 "g_c"
+               OpName %__0 ""
+               OpName %g_tColor "g_tColor"
+               OpName %g_sAniso "g_sAniso"
+               OpName %i_vTextureCoords "i.vTextureCoords"
+               OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 RowMajor
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 Offset 0
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 MatrixStride 16
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 1 Offset 48
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 2 Offset 64
+               OpDecorate %_arr_PerBatchEnvMapConstantBuffer_t_uint_128 ArrayStride 80
+               OpMemberDecorate %_BindlessFastEnvMapCB_PS_t 0 Offset 0
+               OpDecorate %_BindlessFastEnvMapCB_PS_t Block
+               OpDecorate %_ DescriptorSet 0
+               OpDecorate %_ Binding 2
+               OpMemberDecorate %PerViewPushConst_t 0 Offset 0
+               OpDecorate %PerViewPushConst_t Block
+               OpDecorate %g_tColor DescriptorSet 0
+               OpDecorate %g_tColor Binding 0
+               OpDecorate %g_sAniso DescriptorSet 0
+               OpDecorate %g_sAniso Binding 1
+               OpDecorate %i_vTextureCoords Location 0
+               OpDecorate %_entryPointOutput_vColor Location 0
+;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
+;CHECK:               OpDecorate %_struct_111 Block
+;CHECK:               OpMemberDecorate %_struct_111 0 Offset 0
+;CHECK:               OpDecorate %113 DescriptorSet 7
+;CHECK:               OpDecorate %113 Binding 1
+;CHECK:               OpDecorate %_struct_142 Block
+;CHECK:               OpMemberDecorate %_struct_142 0 Offset 0
+;CHECK:               OpMemberDecorate %_struct_142 1 Offset 4
+;CHECK:               OpDecorate %144 DescriptorSet 7
+;CHECK:               OpDecorate %144 Binding 0
+;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+    %v4float = OpTypeVector %float 4
+    %v3float = OpTypeVector %float 3
+%mat4v3float = OpTypeMatrix %v3float 4
+%PerBatchEnvMapConstantBuffer_t = OpTypeStruct %mat4v3float %v4float %v2float
+       %uint = OpTypeInt 32 0
+   %uint_128 = OpConstant %uint 128
+%_arr_PerBatchEnvMapConstantBuffer_t_uint_128 = OpTypeArray %PerBatchEnvMapConstantBuffer_t %uint_128
+%_BindlessFastEnvMapCB_PS_t = OpTypeStruct %_arr_PerBatchEnvMapConstantBuffer_t_uint_128
+%_ptr_Uniform__BindlessFastEnvMapCB_PS_t = OpTypePointer Uniform %_BindlessFastEnvMapCB_PS_t
+          %_ = OpVariable %_ptr_Uniform__BindlessFastEnvMapCB_PS_t Uniform
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+%PerViewPushConst_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewPushConst_t = OpTypePointer PushConstant %PerViewPushConst_t
+        %__0 = OpVariable %_ptr_PushConstant_PerViewPushConst_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+      %int_2 = OpConstant %int 2
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+         %46 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_46 = OpTypePointer UniformConstant %46
+   %g_tColor = OpVariable %_ptr_UniformConstant_46 UniformConstant
+         %50 = OpTypeSampler
+%_ptr_UniformConstant_50 = OpTypePointer UniformConstant %50
+   %g_sAniso = OpVariable %_ptr_UniformConstant_50 UniformConstant
+         %54 = OpTypeSampledImage %46
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+;CHECK:     %uint_0 = OpConstant %uint 0
+;CHECK:    %uint_80 = OpConstant %uint 80
+;CHECK:    %uint_64 = OpConstant %uint 64
+;CHECK:     %uint_7 = OpConstant %uint 7
+;CHECK:     %uint_2 = OpConstant %uint 2
+;CHECK:        %104 = OpTypeFunction %uint %uint %uint %uint %uint
+;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
+;CHECK:%_struct_111 = OpTypeStruct %_runtimearr_uint
+;CHECK:%_ptr_StorageBuffer__struct_111 = OpTypePointer StorageBuffer %_struct_111
+;CHECK:        %113 = OpVariable %_ptr_StorageBuffer__struct_111 StorageBuffer
+;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+;CHECK:       %bool = OpTypeBool
+;CHECK:     %uint_3 = OpConstant %uint 3
+;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:%_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_8 = OpConstant %uint 8
+;CHECK:     %uint_9 = OpConstant %uint 9
+;CHECK:    %uint_10 = OpConstant %uint 10
+;CHECK:    %uint_78 = OpConstant %uint 78
+;CHECK:        %189 = OpConstantNull %v2float
+;CHECK:    %uint_83 = OpConstant %uint 83
+;CHECK:        %201 = OpConstantNull %v4float
+     %MainPs = OpFunction %void None %3
+          %5 = OpLabel
+;CHECK:        %126 = OpFunctionCall %uint %103 %uint_0 %uint_0 %uint_2 %uint_0
+;CHECK:        %191 = OpFunctionCall %uint %103 %uint_0 %uint_0 %uint_0 %uint_0
+;CHECK:               OpBranch %93
+;CHECK:         %93 = OpLabel
+;CHECK:               OpBranch %92
+;CHECK:         %92 = OpLabel
+         %66 = OpLoad %v2float %i_vTextureCoords
+         %79 = OpAccessChain %_ptr_PushConstant_uint %__0 %int_0
+         %80 = OpLoad %uint %79
+         %81 = OpAccessChain %_ptr_Uniform_v2float %_ %int_0 %80 %int_2
+         %82 = OpLoad %v2float %81
+         %86 = OpFAdd %v2float %66 %82
+;CHECK-NOT: %82 = OpLoad %v2float %81
+;CHECK-NOT: %86 = OpFAdd %v2float %66 %82
+;CHECK:         %96 = OpIMul %uint %uint_80 %80
+;CHECK:         %97 = OpIAdd %uint %uint_0 %96
+;CHECK:         %99 = OpIAdd %uint %97 %uint_64
+;CHECK:        %101 = OpIAdd %uint %99 %uint_7
+;CHECK:        %128 = OpULessThan %bool %101 %126
+;CHECK:               OpSelectionMerge %130 None
+;CHECK:               OpBranchConditional %128 %131 %132
+;CHECK:        %131 = OpLabel
+;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:               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
+               OpStore %_entryPointOutput_vColor %91
+;CHECK-NOT: %91 = OpImageSampleImplicitLod %v4float %89 %86
+;CHECK-NOT:       OpStore %_entryPointOutput_vColor %91
+;CHECK:        %192 = OpULessThan %bool %uint_0 %191
+;CHECK:               OpSelectionMerge %193 None
+;CHECK:               OpBranchConditional %192 %194 %195
+;CHECK:        %194 = OpLabel
+;CHECK:        %196 = OpLoad %46 %g_tColor
+;CHECK:        %197 = OpSampledImage %54 %196 %88
+;CHECK:        %198 = OpImageSampleImplicitLod %v4float %197 %86
+;CHECK:               OpBranch %193
+;CHECK:        %195 = OpLabel
+;CHECK:        %200 = OpFunctionCall %void %134 %uint_83 %uint_1 %uint_0 %uint_0 %uint_0
+;CHECK:               OpBranch %193
+;CHECK:        %193 = OpLabel
+;CHECK:        %202 = OpPhi %v4float %198 %194 %201 %195
+;CHECK:               OpStore %_entryPointOutput_vColor %202
+               OpReturn
+               OpFunctionEnd
+;CHECK:        %103 = OpFunction %uint None %104
+;CHECK:        %105 = OpFunctionParameter %uint
+;CHECK:        %106 = OpFunctionParameter %uint
+;CHECK:        %107 = OpFunctionParameter %uint
+;CHECK:        %108 = OpFunctionParameter %uint
+;CHECK:        %109 = OpLabel
+;CHECK:        %115 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %105
+;CHECK:        %116 = OpLoad %uint %115
+;CHECK:        %117 = OpIAdd %uint %116 %106
+;CHECK:        %118 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %117
+;CHECK:        %119 = OpLoad %uint %118
+;CHECK:        %120 = OpIAdd %uint %119 %107
+;CHECK:        %121 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %120
+;CHECK:        %122 = OpLoad %uint %121
+;CHECK:        %123 = OpIAdd %uint %122 %108
+;CHECK:        %124 = OpAccessChain %_ptr_StorageBuffer_uint %113 %uint_0 %123
+;CHECK:        %125 = OpLoad %uint %124
+;CHECK:               OpReturnValue %125
+;CHECK:               OpFunctionEnd
+;CHECK:        %134 = OpFunction %void None %135
+;CHECK:        %136 = OpFunctionParameter %uint
+;CHECK:        %137 = OpFunctionParameter %uint
+;CHECK:        %138 = OpFunctionParameter %uint
+;CHECK:        %139 = OpFunctionParameter %uint
+;CHECK:        %140 = OpFunctionParameter %uint
+;CHECK:        %141 = OpLabel
+;CHECK:        %145 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_0
+;CHECK:        %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:        %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:        %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:        %175 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %174
+;CHECK:               OpStore %175 %172
+;CHECK:        %176 = OpIAdd %uint %148 %uint_7
+;CHECK:        %177 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %176
+;CHECK:               OpStore %177 %137
+;CHECK:        %179 = OpIAdd %uint %148 %uint_8
+;CHECK:        %180 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %179
+;CHECK:               OpStore %180 %138
+;CHECK:        %182 = OpIAdd %uint %148 %uint_9
+;CHECK:        %183 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %182
+;CHECK:               OpStore %183 %139
+;CHECK:        %185 = OpIAdd %uint %148 %uint_10
+;CHECK:        %186 = OpAccessChain %_ptr_StorageBuffer_uint %144 %uint_1 %185
+;CHECK:               OpStore %186 %140
+;CHECK:               OpBranch %152
+;CHECK:        %152 = OpLabel
+;CHECK:               OpReturn
+;CHECK:               OpFunctionEnd
+ )";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, true, true,
+                                               true);
+}
+
+TEST_F(InstBindlessTest, Descriptor16BitIdxRef) {
+  // Check that descriptor indexed with 16bit index is inbounds and
+  // initialized
+  //
+  // Use Simple source with min16uint g_nDataIdx
+
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Int16
+               OpCapability StoragePushConstant16
+;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %_ %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
+;CHECK:               OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %_ %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %60 %gl_FragCoord %119
+               OpExecutionMode %MainPs OriginUpperLeft
+               OpSource HLSL 500
+               OpName %MainPs "MainPs"
+               OpName %g_tColor "g_tColor"
+               OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+               OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+               OpName %_ ""
+               OpName %g_sAniso "g_sAniso"
+               OpName %i_vTextureCoords "i.vTextureCoords"
+               OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+               OpDecorate %g_tColor DescriptorSet 0
+               OpDecorate %g_tColor Binding 0
+               OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+               OpDecorate %PerViewConstantBuffer_t Block
+               OpDecorate %g_sAniso DescriptorSet 0
+               OpDecorate %g_sAniso Binding 0
+               OpDecorate %i_vTextureCoords Location 0
+               OpDecorate %_entryPointOutput_vColor Location 0
+;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
+;CHECK:               OpDecorate %_struct_58 Block
+;CHECK:               OpMemberDecorate %_struct_58 0 Offset 0
+;CHECK:               OpMemberDecorate %_struct_58 1 Offset 4
+;CHECK:               OpDecorate %60 DescriptorSet 7
+;CHECK:               OpDecorate %60 Binding 0
+;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
+;CHECK:               OpDecorate %_struct_117 Block
+;CHECK:               OpMemberDecorate %_struct_117 0 Offset 0
+;CHECK:               OpDecorate %119 DescriptorSet 7
+;CHECK:               OpDecorate %119 Binding 1
+       %void = OpTypeVoid
+         %10 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+    %v4float = OpTypeVector %float 4
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+         %16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+       %uint = OpTypeInt 32 0
+   %uint_128 = OpConstant %uint 128
+%_arr_16_uint_128 = OpTypeArray %16 %uint_128
+%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
+   %g_tColor = OpVariable %_ptr_UniformConstant__arr_16_uint_128 UniformConstant
+     %ushort = OpTypeInt 16 0
+%PerViewConstantBuffer_t = OpTypeStruct %ushort
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+          %_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_ushort = OpTypePointer PushConstant %ushort
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+         %25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+   %g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
+         %27 = OpTypeSampledImage %16
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+;CHECK:     %uint_0 = OpConstant %uint 0
+;CHECK:       %bool = OpTypeBool
+;CHECK:         %51 = OpTypeFunction %void %uint %uint %uint %uint
+;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
+;CHECK: %_struct_58 = OpTypeStruct %uint %_runtimearr_uint
+;CHECK:%_ptr_StorageBuffer__struct_58 = OpTypePointer StorageBuffer %_struct_58
+;CHECK:         %60 = OpVariable %_ptr_StorageBuffer__struct_58 StorageBuffer
+;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+;CHECK:    %uint_10 = OpConstant %uint 10
+;CHECK:     %uint_4 = OpConstant %uint 4
+;CHECK:     %uint_1 = OpConstant %uint 1
+;CHECK:    %uint_23 = OpConstant %uint 23
+;CHECK:     %uint_2 = OpConstant %uint 2
+;CHECK:     %uint_3 = OpConstant %uint 3
+;CHECK:%_ptr_Input_v4float = OpTypePointer Input %v4float
+;CHECK:%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+;CHECK:     %v4uint = OpTypeVector %uint 4
+;CHECK:     %uint_5 = OpConstant %uint 5
+;CHECK:     %uint_7 = OpConstant %uint 7
+;CHECK:     %uint_8 = OpConstant %uint 8
+;CHECK:     %uint_9 = OpConstant %uint 9
+;CHECK:    %uint_60 = OpConstant %uint 60
+;CHECK:        %106 = OpConstantNull %v4float
+;CHECK:        %111 = OpTypeFunction %uint %uint %uint %uint %uint
+;CHECK:%_struct_117 = OpTypeStruct %_runtimearr_uint
+;CHECK:%_ptr_StorageBuffer__struct_117 = OpTypePointer StorageBuffer %_struct_117
+;CHECK:        %119 = OpVariable %_ptr_StorageBuffer__struct_117 StorageBuffer
+     %MainPs = OpFunction %void None %10
+         %30 = OpLabel
+;CHECK:               OpBranch %108
+;CHECK:        %108 = OpLabel
+;CHECK:               OpBranch %39
+;CHECK:         %39 = OpLabel
+         %31 = OpLoad %v2float %i_vTextureCoords
+         %32 = OpAccessChain %_ptr_PushConstant_ushort %_ %int_0
+         %33 = OpLoad %ushort %32
+         %34 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %33
+         %35 = OpLoad %16 %34
+         %36 = OpLoad %25 %g_sAniso
+         %37 = OpSampledImage %27 %35 %36
+         %38 = OpImageSampleImplicitLod %v4float %37 %31
+               OpStore %_entryPointOutput_vColor %38
+;CHECK-NOT:         %38 = OpImageSampleImplicitLod %v4float %37 %31
+;CHECK-NOT:               OpStore %_entryPointOutput_vColor %38
+;CHECK:         %41 = OpUConvert %uint %33
+;CHECK:         %43 = OpULessThan %bool %41 %uint_128
+;CHECK:               OpSelectionMerge %44 None
+;CHECK:               OpBranchConditional %43 %45 %46
+;CHECK:         %45 = OpLabel
+;CHECK:         %47 = OpLoad %16 %34
+;CHECK:         %48 = OpSampledImage %27 %47 %36
+;CHECK:        %109 = OpUConvert %uint %33
+;CHECK:        %131 = OpFunctionCall %uint %110 %uint_0 %uint_0 %uint_0 %109
+;CHECK:        %132 = OpULessThan %bool %uint_0 %131
+;CHECK:               OpSelectionMerge %133 None
+;CHECK:               OpBranchConditional %132 %134 %135
+;CHECK:        %134 = OpLabel
+;CHECK:        %136 = OpLoad %16 %34
+;CHECK:        %137 = OpSampledImage %27 %136 %36
+;CHECK:        %138 = OpImageSampleImplicitLod %v4float %137 %31
+;CHECK:               OpBranch %133
+;CHECK:        %135 = OpLabel
+;CHECK:        %139 = OpUConvert %uint %33
+;CHECK:        %140 = OpFunctionCall %void %50 %uint_60 %uint_1 %139 %uint_0
+;CHECK:               OpBranch %133
+;CHECK:        %133 = OpLabel
+;CHECK:        %141 = OpPhi %v4float %138 %134 %106 %135
+;CHECK:               OpBranch %44
+;CHECK:         %46 = OpLabel
+;CHECK:        %105 = OpFunctionCall %void %50 %uint_60 %uint_0 %41 %uint_128
+;CHECK:               OpBranch %44
+;CHECK:         %44 = OpLabel
+;CHECK:        %107 = OpPhi %v4float %141 %133 %106 %46
+;CHECK:               OpStore %_entryPointOutput_vColor %107
+               OpReturn
+               OpFunctionEnd
+;CHECK:         %50 = OpFunction %void None %51
+;CHECK:         %52 = OpFunctionParameter %uint
+;CHECK:         %53 = OpFunctionParameter %uint
+;CHECK:         %54 = OpFunctionParameter %uint
+;CHECK:         %55 = OpFunctionParameter %uint
+;CHECK:         %56 = OpLabel
+;CHECK:         %62 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_0
+;CHECK:         %65 = OpAtomicIAdd %uint %62 %uint_4 %uint_0 %uint_10
+;CHECK:         %66 = OpIAdd %uint %65 %uint_10
+;CHECK:         %67 = OpArrayLength %uint %60 1
+;CHECK:         %68 = OpULessThanEqual %bool %66 %67
+;CHECK:               OpSelectionMerge %69 None
+;CHECK:               OpBranchConditional %68 %70 %69
+;CHECK:         %70 = OpLabel
+;CHECK:         %71 = OpIAdd %uint %65 %uint_0
+;CHECK:         %73 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %71
+;CHECK:               OpStore %73 %uint_10
+;CHECK:         %75 = OpIAdd %uint %65 %uint_1
+;CHECK:         %76 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %75
+;CHECK:               OpStore %76 %uint_23
+;CHECK:         %78 = OpIAdd %uint %65 %uint_2
+;CHECK:         %79 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %78
+;CHECK:               OpStore %79 %52
+;CHECK:         %81 = OpIAdd %uint %65 %uint_3
+;CHECK:         %82 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %81
+;CHECK:               OpStore %82 %uint_4
+;CHECK:         %85 = OpLoad %v4float %gl_FragCoord
+;CHECK:         %87 = OpBitcast %v4uint %85
+;CHECK:         %88 = OpCompositeExtract %uint %87 0
+;CHECK:         %89 = OpIAdd %uint %65 %uint_4
+;CHECK:         %90 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %89
+;CHECK:               OpStore %90 %88
+;CHECK:         %91 = OpCompositeExtract %uint %87 1
+;CHECK:         %93 = OpIAdd %uint %65 %uint_5
+;CHECK:         %94 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %93
+;CHECK:               OpStore %94 %91
+;CHECK:         %96 = OpIAdd %uint %65 %uint_7
+;CHECK:         %97 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %96
+;CHECK:               OpStore %97 %53
+;CHECK:         %99 = OpIAdd %uint %65 %uint_8
+;CHECK:        %100 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %99
+;CHECK:               OpStore %100 %54
+;CHECK:        %102 = OpIAdd %uint %65 %uint_9
+;CHECK:        %103 = OpAccessChain %_ptr_StorageBuffer_uint %60 %uint_1 %102
+;CHECK:               OpStore %103 %55
+;CHECK:               OpBranch %69
+;CHECK:         %69 = OpLabel
+;CHECK:               OpReturn
+;CHECK:               OpFunctionEnd
+;CHECK:        %110 = OpFunction %uint None %111
+;CHECK:        %112 = OpFunctionParameter %uint
+;CHECK:        %113 = OpFunctionParameter %uint
+;CHECK:        %114 = OpFunctionParameter %uint
+;CHECK:        %115 = OpFunctionParameter %uint
+;CHECK:        %116 = OpLabel
+;CHECK:        %120 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %112
+;CHECK:        %121 = OpLoad %uint %120
+;CHECK:        %122 = OpIAdd %uint %121 %113
+;CHECK:        %123 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %122
+;CHECK:        %124 = OpLoad %uint %123
+;CHECK:        %125 = OpIAdd %uint %124 %114
+;CHECK:        %126 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %125
+;CHECK:        %127 = OpLoad %uint %126
+;CHECK:        %128 = OpIAdd %uint %127 %115
+;CHECK:        %129 = OpAccessChain %_ptr_StorageBuffer_uint %119 %uint_0 %128
+;CHECK:        %130 = OpLoad %uint %129
+;CHECK:               OpReturnValue %130
+;CHECK:               OpFunctionEnd
+ )";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, true, true,
+                                               false);
+}
+
+TEST_F(InstBindlessTest, UniformArray16bitIdxRef) {
+  // Check that uniform array ref with 16bit index does not go out-of-bounds.
+  //
+  // Texture2D g_tColor;
+  // SamplerState g_sAniso;
+  //
+  // layout(push_constant) cbuffer PerViewPushConst_t { min16uint g_c; };
+  //
+  // struct PerBatchEnvMapConstantBuffer_t {
+  //   float4x3 g_matEnvMapWorldToLocal;
+  //   float4 g_vEnvironmentMapBoxMins;
+  //   float2 g_TexOff;
+  // };
+  //
+  // cbuffer _BindlessFastEnvMapCB_PS_t {
+  //   PerBatchEnvMapConstantBuffer_t g_envMapConstants[128];
+  // };
+  //
+  // struct PS_INPUT {
+  //   float2 vTextureCoords : TEXCOORD2;
+  // };
+  //
+  // struct PS_OUTPUT {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i) {
+  //   PS_OUTPUT ps_output;
+  //   float2 off;
+  //   float2 vtc;
+  //   off = g_envMapConstants[g_c].g_TexOff;
+  //   vtc = i.vTextureCoords.xy + off;
+  //   ps_output.vColor = g_tColor.Sample(g_sAniso, vtc);
+  //   return ps_output;
+  // }
+
+  const std::string text = R"(
+               OpCapability Shader
+               OpCapability Int16
+               OpCapability StoragePushConstant16
+;CHECK:               OpExtension "SPV_KHR_storage_buffer_storage_class"
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor
+;CHECK:               OpEntryPoint Fragment %MainPs "MainPs" %_ %__0 %g_tColor %g_sAniso %i_vTextureCoords %_entryPointOutput_vColor %69 %97 %gl_FragCoord
+               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"
+               OpMemberName %_BindlessFastEnvMapCB_PS_t 0 "g_envMapConstants"
+               OpName %_ ""
+               OpName %PerViewPushConst_t "PerViewPushConst_t"
+               OpMemberName %PerViewPushConst_t 0 "g_c"
+               OpName %__0 ""
+               OpName %g_tColor "g_tColor"
+               OpName %g_sAniso "g_sAniso"
+               OpName %i_vTextureCoords "i.vTextureCoords"
+               OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 RowMajor
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 Offset 0
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 0 MatrixStride 16
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 1 Offset 48
+               OpMemberDecorate %PerBatchEnvMapConstantBuffer_t 2 Offset 64
+               OpDecorate %_arr_PerBatchEnvMapConstantBuffer_t_uint_128 ArrayStride 80
+               OpMemberDecorate %_BindlessFastEnvMapCB_PS_t 0 Offset 0
+               OpDecorate %_BindlessFastEnvMapCB_PS_t Block
+               OpDecorate %_ DescriptorSet 0
+               OpDecorate %_ Binding 0
+               OpMemberDecorate %PerViewPushConst_t 0 Offset 0
+               OpDecorate %PerViewPushConst_t Block
+               OpDecorate %g_tColor DescriptorSet 0
+               OpDecorate %g_tColor Binding 0
+               OpDecorate %g_sAniso DescriptorSet 0
+               OpDecorate %g_sAniso Binding 0
+               OpDecorate %i_vTextureCoords Location 0
+               OpDecorate %_entryPointOutput_vColor Location 0
+;CHECK:               OpDecorate %_runtimearr_uint ArrayStride 4
+;CHECK:               OpDecorate %_struct_67 Block
+;CHECK:               OpMemberDecorate %_struct_67 0 Offset 0
+;CHECK:               OpDecorate %69 DescriptorSet 7
+;CHECK:               OpDecorate %69 Binding 1
+;CHECK:               OpDecorate %_struct_95 Block
+;CHECK:               OpMemberDecorate %_struct_95 0 Offset 0
+;CHECK:               OpMemberDecorate %_struct_95 1 Offset 4
+;CHECK:               OpDecorate %97 DescriptorSet 7
+;CHECK:               OpDecorate %97 Binding 0
+;CHECK:               OpDecorate %gl_FragCoord BuiltIn FragCoord
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+    %v4float = OpTypeVector %float 4
+    %v3float = OpTypeVector %float 3
+%mat4v3float = OpTypeMatrix %v3float 4
+%PerBatchEnvMapConstantBuffer_t = OpTypeStruct %mat4v3float %v4float %v2float
+       %uint = OpTypeInt 32 0
+   %uint_128 = OpConstant %uint 128
+%_arr_PerBatchEnvMapConstantBuffer_t_uint_128 = OpTypeArray %PerBatchEnvMapConstantBuffer_t %uint_128
+%_BindlessFastEnvMapCB_PS_t = OpTypeStruct %_arr_PerBatchEnvMapConstantBuffer_t_uint_128
+%_ptr_Uniform__BindlessFastEnvMapCB_PS_t = OpTypePointer Uniform %_BindlessFastEnvMapCB_PS_t
+          %_ = OpVariable %_ptr_Uniform__BindlessFastEnvMapCB_PS_t Uniform
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+     %ushort = OpTypeInt 16 0
+%PerViewPushConst_t = OpTypeStruct %ushort
+%_ptr_PushConstant_PerViewPushConst_t = OpTypePointer PushConstant %PerViewPushConst_t
+        %__0 = OpVariable %_ptr_PushConstant_PerViewPushConst_t PushConstant
+%_ptr_PushConstant_ushort = OpTypePointer PushConstant %ushort
+      %int_2 = OpConstant %int 2
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+         %30 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_30 = OpTypePointer UniformConstant %30
+   %g_tColor = OpVariable %_ptr_UniformConstant_30 UniformConstant
+         %32 = OpTypeSampler
+%_ptr_UniformConstant_32 = OpTypePointer UniformConstant %32
+   %g_sAniso = OpVariable %_ptr_UniformConstant_32 UniformConstant
+         %34 = OpTypeSampledImage %30
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+;CHECK:     %uint_0 = OpConstant %uint 0
+;CHECK:    %uint_80 = OpConstant %uint 80
+;CHECK:    %uint_64 = OpConstant %uint 64
+;CHECK:     %uint_7 = OpConstant %uint 7
+;CHECK:     %uint_1 = OpConstant %uint 1
+;CHECK:         %61 = OpTypeFunction %uint %uint %uint %uint
+;CHECK:%_runtimearr_uint = OpTypeRuntimeArray %uint
+;CHECK: %_struct_67 = OpTypeStruct %_runtimearr_uint
+;CHECK:%_ptr_StorageBuffer__struct_67 = OpTypePointer StorageBuffer %_struct_67
+;CHECK:         %69 = OpVariable %_ptr_StorageBuffer__struct_67 StorageBuffer
+;CHECK:%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+;CHECK:       %bool = OpTypeBool
+;CHECK:     %uint_3 = OpConstant %uint 3
+;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:%_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_8 = OpConstant %uint 8
+;CHECK:     %uint_9 = OpConstant %uint 9
+;CHECK:    %uint_10 = OpConstant %uint 10
+;CHECK:    %uint_81 = OpConstant %uint 81
+;CHECK:        %142 = OpConstantNull %v2float
+     %MainPs = OpFunction %void None %14
+         %37 = OpLabel
+;CHECK:         %79 = OpFunctionCall %uint %60 %uint_1 %uint_0 %uint_0
+;CHECK:               OpBranch %49
+;CHECK:         %49 = OpLabel
+;CHECK:               OpBranch %48
+;CHECK:         %48 = OpLabel
+         %38 = OpLoad %v2float %i_vTextureCoords
+         %39 = OpAccessChain %_ptr_PushConstant_ushort %__0 %int_0
+         %40 = OpLoad %ushort %39
+         %41 = OpAccessChain %_ptr_Uniform_v2float %_ %int_0 %40 %int_2
+         %42 = OpLoad %v2float %41
+         %43 = OpFAdd %v2float %38 %42
+;CHECK-NOT:     %42 = OpLoad %v2float %41
+;CHECK-NOT:     %43 = OpFAdd %v2float %38 %42
+;CHECK:         %52 = OpUConvert %uint %40
+;CHECK:         %53 = OpIMul %uint %uint_80 %52
+;CHECK:         %54 = OpIAdd %uint %uint_0 %53
+;CHECK:         %56 = OpIAdd %uint %54 %uint_64
+;CHECK:         %58 = OpIAdd %uint %56 %uint_7
+;CHECK:         %81 = OpULessThan %bool %58 %79
+;CHECK:               OpSelectionMerge %83 None
+;CHECK:               OpBranchConditional %81 %84 %85
+;CHECK:         %84 = OpLabel
+;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:               OpBranch %83
+;CHECK:         %83 = OpLabel
+;CHECK:        %143 = OpPhi %v2float %86 %84 %142 %85
+;CHECK:         %43 = OpFAdd %v2float %38 %143
+         %44 = OpLoad %30 %g_tColor
+         %45 = OpLoad %32 %g_sAniso
+         %46 = OpSampledImage %34 %44 %45
+         %47 = OpImageSampleImplicitLod %v4float %46 %43
+               OpStore %_entryPointOutput_vColor %47
+               OpReturn
+               OpFunctionEnd
+;CHECK:         %60 = OpFunction %uint None %61
+;CHECK:         %62 = OpFunctionParameter %uint
+;CHECK:         %63 = OpFunctionParameter %uint
+;CHECK:         %64 = OpFunctionParameter %uint
+;CHECK:         %65 = OpLabel
+;CHECK:         %71 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0 %62
+;CHECK:         %72 = OpLoad %uint %71
+;CHECK:         %73 = OpIAdd %uint %72 %63
+;CHECK:         %74 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0 %73
+;CHECK:         %75 = OpLoad %uint %74
+;CHECK:         %76 = OpIAdd %uint %75 %64
+;CHECK:         %77 = OpAccessChain %_ptr_StorageBuffer_uint %69 %uint_0 %76
+;CHECK:         %78 = OpLoad %uint %77
+;CHECK:               OpReturnValue %78
+;CHECK:               OpFunctionEnd
+;CHECK:         %87 = OpFunction %void None %88
+;CHECK:         %89 = OpFunctionParameter %uint
+;CHECK:         %90 = OpFunctionParameter %uint
+;CHECK:         %91 = OpFunctionParameter %uint
+;CHECK:         %92 = OpFunctionParameter %uint
+;CHECK:         %93 = OpFunctionParameter %uint
+;CHECK:         %94 = OpLabel
+;CHECK:         %98 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_0
+;CHECK:        %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:        %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:        %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:        %128 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %127
+;CHECK:               OpStore %128 %125
+;CHECK:        %129 = OpIAdd %uint %101 %uint_7
+;CHECK:        %130 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %129
+;CHECK:               OpStore %130 %90
+;CHECK:        %132 = OpIAdd %uint %101 %uint_8
+;CHECK:        %133 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %132
+;CHECK:               OpStore %133 %91
+;CHECK:        %135 = OpIAdd %uint %101 %uint_9
+;CHECK:        %136 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %135
+;CHECK:               OpStore %136 %92
+;CHECK:        %138 = OpIAdd %uint %101 %uint_10
+;CHECK:        %139 = OpAccessChain %_ptr_StorageBuffer_uint %97 %uint_1 %138
+;CHECK:               OpStore %139 %93
+;CHECK:               OpBranch %105
+;CHECK:        %105 = OpLabel
+;CHECK:               OpReturn
+;CHECK:               OpFunctionEnd
+ )";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<InstBindlessCheckPass>(text, true, 7u, 23u, false,
+                                               false, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //   Compute shader
diff --git a/test/opt/ir_context_test.cpp b/test/opt/ir_context_test.cpp
index 437fe73..b6866d0 100644
--- a/test/opt/ir_context_test.cpp
+++ b/test/opt/ir_context_test.cpp
@@ -37,22 +37,22 @@
 using ::testing::Each;
 using ::testing::UnorderedElementsAre;
 
-class DummyPassPreservesNothing : public Pass {
+class NoopPassPreservesNothing : public Pass {
  public:
-  DummyPassPreservesNothing(Status s) : Pass(), status_to_return_(s) {}
+  NoopPassPreservesNothing(Status s) : Pass(), status_to_return_(s) {}
 
-  const char* name() const override { return "dummy-pass"; }
+  const char* name() const override { return "noop-pass"; }
   Status Process() override { return status_to_return_; }
 
  private:
   Status status_to_return_;
 };
 
-class DummyPassPreservesAll : public Pass {
+class NoopPassPreservesAll : public Pass {
  public:
-  DummyPassPreservesAll(Status s) : Pass(), status_to_return_(s) {}
+  NoopPassPreservesAll(Status s) : Pass(), status_to_return_(s) {}
 
-  const char* name() const override { return "dummy-pass"; }
+  const char* name() const override { return "noop-pass"; }
   Status Process() override { return status_to_return_; }
 
   Analysis GetPreservedAnalyses() override {
@@ -63,11 +63,11 @@
   Status status_to_return_;
 };
 
-class DummyPassPreservesFirst : public Pass {
+class NoopPassPreservesFirst : public Pass {
  public:
-  DummyPassPreservesFirst(Status s) : Pass(), status_to_return_(s) {}
+  NoopPassPreservesFirst(Status s) : Pass(), status_to_return_(s) {}
 
-  const char* name() const override { return "dummy-pass"; }
+  const char* name() const override { return "noop-pass"; }
   Status Process() override { return status_to_return_; }
 
   Analysis GetPreservedAnalyses() override { return IRContext::kAnalysisBegin; }
@@ -116,7 +116,7 @@
     built_analyses |= i;
   }
 
-  DummyPassPreservesNothing pass(Pass::Status::SuccessWithoutChange);
+  NoopPassPreservesNothing pass(Pass::Status::SuccessWithoutChange);
   Pass::Status s = pass.Run(&localContext);
   EXPECT_EQ(s, Pass::Status::SuccessWithoutChange);
   EXPECT_TRUE(localContext.AreAnalysesValid(built_analyses));
@@ -132,7 +132,7 @@
     localContext.BuildInvalidAnalyses(i);
   }
 
-  DummyPassPreservesNothing pass(Pass::Status::SuccessWithChange);
+  NoopPassPreservesNothing pass(Pass::Status::SuccessWithChange);
   Pass::Status s = pass.Run(&localContext);
   EXPECT_EQ(s, Pass::Status::SuccessWithChange);
   for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
@@ -151,7 +151,7 @@
     localContext.BuildInvalidAnalyses(i);
   }
 
-  DummyPassPreservesAll pass(Pass::Status::SuccessWithChange);
+  NoopPassPreservesAll pass(Pass::Status::SuccessWithChange);
   Pass::Status s = pass.Run(&localContext);
   EXPECT_EQ(s, Pass::Status::SuccessWithChange);
   for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
@@ -170,7 +170,7 @@
     localContext.BuildInvalidAnalyses(i);
   }
 
-  DummyPassPreservesFirst pass(Pass::Status::SuccessWithChange);
+  NoopPassPreservesFirst pass(Pass::Status::SuccessWithChange);
   Pass::Status s = pass.Run(&localContext);
   EXPECT_EQ(s, Pass::Status::SuccessWithChange);
   EXPECT_TRUE(localContext.AreAnalysesValid(IRContext::kAnalysisBegin));
@@ -912,7 +912,7 @@
       BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                   SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo);
-  DummyPassPreservesAll pass(Pass::Status::SuccessWithChange);
+  NoopPassPreservesAll pass(Pass::Status::SuccessWithChange);
   pass.Run(ctx.get());
   EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo));
 
@@ -978,7 +978,7 @@
       BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                   SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo);
-  DummyPassPreservesAll pass(Pass::Status::SuccessWithChange);
+  NoopPassPreservesAll pass(Pass::Status::SuccessWithChange);
   pass.Run(ctx.get());
   EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo));
 
@@ -1011,6 +1011,71 @@
       dbg_decl1->GetSingleWordOperand(kDebugDeclareOperandVariableIndex) == 20);
 }
 
+TEST_F(IRContextTest, DebugInstructionReplaceDebugScopeAndDebugInlinedAt) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+%2 = OpString "test"
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeFloat 32
+%6 = OpTypePointer Function %5
+%7 = OpConstant %5 0
+%8 = OpTypeInt 32 0
+%9 = OpConstant %8 32
+%10 = OpExtInst %3 %1 DebugExpression
+%11 = OpExtInst %3 %1 DebugSource %2
+%12 = OpExtInst %3 %1 DebugCompilationUnit 1 4 %11 HLSL
+%13 = OpExtInst %3 %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %3
+%14 = OpExtInst %3 %1 DebugFunction %2 %13 %11 0 0 %12 %2 FlagIsProtected|FlagIsPrivate 0 %17
+%15 = OpExtInst %3 %1 DebugInfoNone
+%16 = OpExtInst %3 %1 DebugFunction %2 %13 %11 10 10 %12 %2 FlagIsProtected|FlagIsPrivate 0 %15
+%25 = OpExtInst %3 %1 DebugInlinedAt 0 %14
+%26 = OpExtInst %3 %1 DebugInlinedAt 2 %14
+%17 = OpFunction %3 None %4
+%18 = OpLabel
+%19 = OpExtInst %3 %1 DebugScope %14
+%20 = OpVariable %6 Function
+OpBranch %21
+%21 = OpLabel
+%24 = OpExtInst %3 %1 DebugScope %16
+%22 = OpPhi %5 %7 %18
+OpBranch %23
+%23 = OpLabel
+%27 = OpExtInst %3 %1 DebugScope %16 %25
+OpLine %2 0 0
+%28 = OpFAdd %5 %7 %7
+OpStore %20 %28
+OpReturn
+OpFunctionEnd)";
+
+  std::unique_ptr<IRContext> ctx =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo);
+  NoopPassPreservesAll pass(Pass::Status::SuccessWithChange);
+  pass.Run(ctx.get());
+  EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo));
+
+  auto* inst0 = ctx->get_def_use_mgr()->GetDef(20);
+  auto* inst1 = ctx->get_def_use_mgr()->GetDef(22);
+  auto* inst2 = ctx->get_def_use_mgr()->GetDef(28);
+  EXPECT_EQ(inst0->GetDebugScope().GetLexicalScope(), 14);
+  EXPECT_EQ(inst1->GetDebugScope().GetLexicalScope(), 16);
+  EXPECT_EQ(inst2->GetDebugScope().GetLexicalScope(), 16);
+  EXPECT_EQ(inst2->GetDebugInlinedAt(), 25);
+
+  EXPECT_TRUE(ctx->ReplaceAllUsesWith(14, 12));
+  EXPECT_TRUE(ctx->ReplaceAllUsesWith(16, 14));
+  EXPECT_TRUE(ctx->ReplaceAllUsesWith(25, 26));
+  EXPECT_EQ(inst0->GetDebugScope().GetLexicalScope(), 12);
+  EXPECT_EQ(inst1->GetDebugScope().GetLexicalScope(), 14);
+  EXPECT_EQ(inst2->GetDebugScope().GetLexicalScope(), 14);
+  EXPECT_EQ(inst2->GetDebugInlinedAt(), 26);
+}
+
 TEST_F(IRContextTest, AddDebugValueAfterReplaceUse) {
   const std::string text = R"(
 OpCapability Shader
@@ -1055,7 +1120,7 @@
       BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                   SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
   ctx->BuildInvalidAnalyses(IRContext::kAnalysisDebugInfo);
-  DummyPassPreservesAll pass(Pass::Status::SuccessWithChange);
+  NoopPassPreservesAll pass(Pass::Status::SuccessWithChange);
   pass.Run(ctx.get());
   EXPECT_TRUE(ctx->AreAnalysesValid(IRContext::kAnalysisDebugInfo));
 
@@ -1070,11 +1135,13 @@
 
   // No DebugValue should be added because result id '26' is not used for
   // DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 26, 22, dbg_decl);
+  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 26, 22,
+                                                             dbg_decl, nullptr);
   EXPECT_EQ(dbg_decl->NextNode()->opcode(), SpvOpReturn);
 
   // DebugValue should be added because result id '20' is used for DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 20, 22, dbg_decl);
+  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 20, 22,
+                                                             dbg_decl, nullptr);
   EXPECT_EQ(dbg_decl->NextNode()->GetOpenCL100DebugOpcode(),
             OpenCLDebugInfo100DebugValue);
 
@@ -1087,13 +1154,15 @@
 
   // No DebugValue should be added because result id '20' is not used for
   // DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 20, 7, dbg_decl);
+  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 20, 7,
+                                                             dbg_decl, nullptr);
   Instruction* dbg_value = dbg_decl->NextNode();
   EXPECT_EQ(dbg_value->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue);
   EXPECT_EQ(dbg_value->GetSingleWordOperand(kDebugValueOperandValueIndex), 22);
 
   // DebugValue should be added because result id '26' is used for DebugDeclare.
-  ctx->get_debug_info_mgr()->AddDebugValue(dbg_decl, 26, 7, dbg_decl);
+  ctx->get_debug_info_mgr()->AddDebugValueIfVarDeclIsVisible(dbg_decl, 26, 7,
+                                                             dbg_decl, nullptr);
   dbg_value = dbg_decl->NextNode();
   EXPECT_EQ(dbg_value->GetOpenCL100DebugOpcode(), OpenCLDebugInfo100DebugValue);
   EXPECT_EQ(dbg_value->GetSingleWordOperand(kDebugValueOperandValueIndex), 7);
diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp
index 2104492..8b77aa3 100644
--- a/test/opt/ir_loader_test.cpp
+++ b/test/opt/ir_loader_test.cpp
@@ -21,6 +21,7 @@
 
 #include "gtest/gtest.h"
 #include "source/opt/build_module.h"
+#include "source/opt/def_use_manager.h"
 #include "source/opt/ir_context.h"
 #include "spirv-tools/libspirv.hpp"
 
@@ -28,6 +29,8 @@
 namespace opt {
 namespace {
 
+constexpr uint32_t kOpLineOperandLineIndex = 1;
+
 void DoRoundTripCheck(const std::string& text) {
   SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
   std::unique_ptr<IRContext> context =
@@ -129,6 +132,110 @@
   // clang-format on
 }
 
+TEST(IrBuilder, DistributeLineDebugInfo) {
+  const std::string text =
+      // clang-format off
+               "OpCapability Shader\n"
+          "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+               "OpMemoryModel Logical GLSL450\n"
+               "OpEntryPoint Vertex %main \"main\"\n"
+               "OpSource ESSL 310\n"
+       "%file = OpString \"test\"\n"
+               "OpName %main \"main\"\n"
+               "OpName %f_ \"f(\"\n"
+               "OpName %gv1 \"gv1\"\n"
+               "OpName %gv2 \"gv2\"\n"
+               "OpName %lv1 \"lv1\"\n"
+               "OpName %lv2 \"lv2\"\n"
+               "OpName %lv1_0 \"lv1\"\n"
+       "%void = OpTypeVoid\n"
+         "%10 = OpTypeFunction %void\n"
+               "OpLine %file 10 0\n"
+      "%float = OpTypeFloat 32\n"
+         "%12 = OpTypeFunction %float\n"
+ "%_ptr_Private_float = OpTypePointer Private %float\n"
+        "%gv1 = OpVariable %_ptr_Private_float Private\n"
+   "%float_10 = OpConstant %float 10\n"
+        "%gv2 = OpVariable %_ptr_Private_float Private\n"
+  "%float_100 = OpConstant %float 100\n"
+ "%_ptr_Function_float = OpTypePointer Function %float\n"
+       "%main = OpFunction %void None %10\n"
+         "%17 = OpLabel\n"
+      "%lv1_0 = OpVariable %_ptr_Function_float Function\n"
+               "OpStore %gv1 %float_10\n"
+               "OpStore %gv2 %float_100\n"
+               "OpLine %file 1 0\n"
+               "OpNoLine\n"
+               "OpLine %file 2 0\n"
+         "%18 = OpLoad %float %gv1\n"
+         "%19 = OpLoad %float %gv2\n"
+         "%20 = OpFSub %float %18 %19\n"
+               "OpStore %lv1_0 %20\n"
+               "OpReturn\n"
+               "OpFunctionEnd\n"
+         "%f_ = OpFunction %float None %12\n"
+         "%21 = OpLabel\n"
+        "%lv1 = OpVariable %_ptr_Function_float Function\n"
+        "%lv2 = OpVariable %_ptr_Function_float Function\n"
+               "OpLine %file 3 0\n"
+               "OpLine %file 4 0\n"
+         "%22 = OpLoad %float %gv1\n"
+         "%23 = OpLoad %float %gv2\n"
+         "%24 = OpFAdd %float %22 %23\n"
+               "OpStore %lv1 %24\n"
+               "OpLine %file 5 0\n"
+               "OpLine %file 6 0\n"
+               "OpNoLine\n"
+         "%25 = OpLoad %float %gv1\n"
+         "%26 = OpLoad %float %gv2\n"
+         "%27 = OpFMul %float %25 %26\n"
+               "OpBranch %28\n"
+         "%28 = OpLabel\n"
+               "OpStore %lv2 %27\n"
+         "%29 = OpLoad %float %lv1\n"
+               "OpLine %file 7 0\n"
+         "%30 = OpLoad %float %lv2\n"
+         "%31 = OpFDiv %float %28 %29\n"
+               "OpReturnValue %30\n"
+               "OpFunctionEnd\n";
+  // clang-format on
+
+  std::unique_ptr<IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  ASSERT_NE(nullptr, context);
+
+  struct LineInstrCheck {
+    uint32_t id;
+    std::vector<uint32_t> line_numbers;
+  };
+  const uint32_t kNoLine = 0;
+  const LineInstrCheck line_checks[] = {
+      {12, {10}},   {18, {1, kNoLine, 2}},
+      {19, {2}},    {20, {2}},
+      {22, {3, 4}}, {23, {4}},
+      {24, {4}},    {25, {5, 6, kNoLine}},
+      {26, {}},     {27, {}},
+      {28, {}},     {29, {}},
+      {30, {7}},    {31, {7}},
+  };
+
+  spvtools::opt::analysis::DefUseManager* def_use_mgr =
+      context->get_def_use_mgr();
+  for (const LineInstrCheck& check : line_checks) {
+    auto& lines = def_use_mgr->GetDef(check.id)->dbg_line_insts();
+    for (uint32_t i = 0; i < check.line_numbers.size(); ++i) {
+      if (check.line_numbers[i] == kNoLine) {
+        EXPECT_EQ(lines[i].opcode(), SpvOpNoLine);
+        continue;
+      }
+      EXPECT_EQ(lines[i].opcode(), SpvOpLine);
+      EXPECT_EQ(lines[i].GetSingleWordOperand(kOpLineOperandLineIndex),
+                check.line_numbers[i]);
+    }
+  }
+}
+
 TEST(IrBuilder, ConsumeDebugInfoInst) {
   // /* HLSL */
   //
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp
index 39899e3..6fcf23f 100644
--- a/test/opt/local_access_chain_convert_test.cpp
+++ b/test/opt/local_access_chain_convert_test.cpp
@@ -96,6 +96,205 @@
                                                      true);
 }
 
+TEST_F(LocalAccessChainConvertTest, DebugScopeAndLineInfoForNewInstructions) {
+  //  #version 140
+  //
+  //  in vec4 BaseColor;
+  //
+  //  struct S_t {
+  //      vec4 v0;
+  //      vec4 v1;
+  //  };
+  //
+  //  void main()
+  //  {
+  //      S_t s0;
+  //      s0.v1 = BaseColor;
+  //      gl_FragColor = s0.v1;
+  //  }
+
+  const std::string predefs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+%ext = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%5 = OpString "ps.hlsl"
+%6 = OpString "float"
+%var_name = OpString "s0"
+%main_name = OpString "main"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%int_32 = OpConstant %int 32
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%20 = OpExtInst %void %ext DebugSource %5
+%21 = OpExtInst %void %ext DebugCompilationUnit 1 4 %20 HLSL
+%22 = OpExtInst %void %ext DebugTypeBasic %6 %int_32 Float
+%23 = OpExtInst %void %ext DebugTypeVector %22 4
+%24 = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %23
+%dbg_main = OpExtInst %void %ext DebugFunction %main_name %24 %20 4 1 %21 %main_name FlagIsProtected|FlagIsPrivate 4 %main
+%25 = OpExtInst %void %ext DebugLocalVariable %var_name %23 %20 0 0 %dbg_main FlagIsLocal
+)";
+
+  const std::string before =
+      R"(
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: OpLine {{%\w+}} 1 0
+; CHECK: [[ld1:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex1:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[ld1]] 1
+; CHECK: OpStore %s0 [[ex1]]
+; CHECK: OpLine {{%\w+}} 3 0
+; CHECK: [[ld2:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1
+; CHECK: OpLine {{%\w+}} 4 0
+; CHECK: OpStore %gl_FragColor [[ex2]]
+%main = OpFunction %void None %8
+%17 = OpLabel
+%26 = OpExtInst %void %ext DebugScope %dbg_main
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+OpLine %5 0 0
+%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpLine %5 1 0
+OpStore %19 %18
+OpLine %5 2 0
+%27 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpLine %5 3 0
+%28 = OpLoad %v4float %27
+OpLine %5 4 0
+OpStore %gl_FragColor %28
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+                                                     true);
+}
+
+TEST_F(LocalAccessChainConvertTest, TestTargetsReferencedByDebugValue) {
+  //  #version 140
+  //
+  //  in vec4 BaseColor;
+  //
+  //  struct S_t {
+  //      vec4 v0;
+  //      vec4 v1;
+  //  };
+  //
+  //  void main()
+  //  {
+  //      S_t s0;
+  //      s0.v1 = BaseColor;
+  //      gl_FragColor = s0.v1;
+  //  }
+
+  const std::string predefs_before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+%ext = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%5 = OpString "ps.hlsl"
+%6 = OpString "float"
+%var_name = OpString "s0"
+%main_name = OpString "main"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%int_32 = OpConstant %int 32
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%deref = OpExtInst %void %ext DebugOperation Deref
+%deref_expr = OpExtInst %void %ext DebugExpression %deref
+%null_expr = OpExtInst %void %ext DebugExpression
+%20 = OpExtInst %void %ext DebugSource %5
+%21 = OpExtInst %void %ext DebugCompilationUnit 1 4 %20 HLSL
+%22 = OpExtInst %void %ext DebugTypeBasic %6 %int_32 Float
+%23 = OpExtInst %void %ext DebugTypeVector %22 4
+%24 = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %23
+%dbg_main = OpExtInst %void %ext DebugFunction %main_name %24 %20 4 1 %21 %main_name FlagIsProtected|FlagIsPrivate 4 %main
+%25 = OpExtInst %void %ext DebugLocalVariable %var_name %23 %20 0 0 %dbg_main FlagIsLocal
+)";
+
+  const std::string before =
+      R"(
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: OpLine {{%\w+}} 0 0
+; CHECK: [[s0_1_ptr:%\w+]] = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+; CHECK: DebugValue [[dbg_s0:%\w+]] [[s0_1_ptr]]
+; CHECK: OpLine {{%\w+}} 1 0
+; CHECK: [[s0:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[comp:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[s0]] 1
+; CHECK: OpStore %s0 [[comp]]
+; CHECK: OpLine {{%\w+}} 2 0
+; CHECK: [[s0_2_ptr:%\w+]] = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+; CHECK: OpLine {{%\w+}} 3 0
+; CHECK: [[s0:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[s0_2_val:%\w+]] = OpCompositeExtract %v4float [[s0]] 1
+; CHECK: DebugValue [[dbg_s0]] [[s0_2_val]]
+; CHECK: OpLine {{%\w+}} 4 0
+; CHECK: OpStore %gl_FragColor [[s0_2_val]]
+%main = OpFunction %void None %8
+%17 = OpLabel
+%26 = OpExtInst %void %ext DebugScope %dbg_main
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+OpLine %5 0 0
+%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+%29 = OpExtInst %void %ext DebugValue %25 %19 %deref_expr %int_1
+OpLine %5 1 0
+OpStore %19 %18
+OpLine %5 2 0
+%27 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpLine %5 3 0
+%28 = OpLoad %v4float %27
+%30 = OpExtInst %void %ext DebugValue %25 %28 %null_expr %int_1
+OpLine %5 4 0
+OpStore %gl_FragColor %28
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+                                                     true);
+}
+
 TEST_F(LocalAccessChainConvertTest, InBoundsAccessChainsConverted) {
   //  #version 140
   //
@@ -927,6 +1126,37 @@
   EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
 }
 
+TEST_F(LocalAccessChainConvertTest, AccessChainWithNoIndex) {
+  const std::string before =
+      R"(
+; CHECK: OpFunction
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: OpStore [[var]] %true
+; CHECK: OpLoad %bool [[var]]
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+%_ptr_Function_bool = OpTypePointer Function %bool
+          %2 = OpFunction %void None %4
+          %8 = OpLabel
+          %9 = OpVariable %_ptr_Function_bool Function
+         %10 = OpAccessChain %_ptr_Function_bool %9
+               OpStore %10 %true
+         %11 = OpLoad %bool %10
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<LocalAccessChainConvertPass>(before, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Assorted vector and matrix types
diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp
index 6581ebf..2ecd238 100644
--- a/test/opt/local_ssa_elim_test.cpp
+++ b/test/opt/local_ssa_elim_test.cpp
@@ -2165,6 +2165,341 @@
   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
+  // DebugValue for the value assignment of function argument even when it is an
+  // inlined function. It has to check the visibility Phi through all its value
+  // operands. See the DebugValue for "int i" of "foo()" in the following code.
+  //
+  // struct VS_OUTPUT {
+  //   float4 pos : SV_POSITION;
+  //   float4 color : COLOR;
+  // };
+  //
+  // float4 foo(int i, float4 pos) {
+  //   while (i < pos.x) {
+  //     pos = pos.x + i;
+  //     ++i;
+  //   }
+  //   return pos;
+  // }
+  //
+  // VS_OUTPUT main(float4 pos : POSITION,
+  //                float4 color : COLOR) {
+  //   VS_OUTPUT vout;
+  //   vout.pos = foo(4, pos);
+  //   vout.color = color;
+  //   return vout;
+  // }
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %in_var_POSITION %in_var_COLOR %gl_Position %out_var_COLOR
+          %7 = OpString "vertex.hlsl"
+          %8 = OpString "float"
+          %9 = OpString "VS_OUTPUT"
+         %10 = OpString "color"
+         %11 = OpString "pos"
+         %12 = OpString "int"
+         %13 = OpString "foo"
+         %14 = OpString ""
+         %15 = OpString "i"
+         %16 = OpString "main"
+         %17 = OpString "vout"
+               OpName %in_var_POSITION "in.var.POSITION"
+               OpName %in_var_COLOR "in.var.COLOR"
+               OpName %out_var_COLOR "out.var.COLOR"
+               OpName %main "main"
+               OpName %param_var_pos "param.var.pos"
+               OpName %param_var_color "param.var.color"
+               OpName %VS_OUTPUT "VS_OUTPUT"
+               OpMemberName %VS_OUTPUT 0 "pos"
+               OpMemberName %VS_OUTPUT 1 "color"
+               OpDecorate %gl_Position BuiltIn Position
+               OpDecorate %in_var_POSITION Location 0
+               OpDecorate %in_var_COLOR Location 1
+               OpDecorate %out_var_COLOR Location 0
+        %int = OpTypeInt 32 1
+      %int_4 = OpConstant %int 4
+      %int_0 = OpConstant %int 0
+      %int_1 = OpConstant %int 1
+       %uint = OpTypeInt 32 0
+    %uint_32 = OpConstant %uint 32
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+   %uint_256 = OpConstant %uint 256
+   %uint_128 = OpConstant %uint 128
+     %uint_0 = OpConstant %uint 0
+         %50 = OpTypeFunction %void
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+  %VS_OUTPUT = OpTypeStruct %v4float %v4float
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Function_float = OpTypePointer Function %float
+       %bool = OpTypeBool
+%in_var_POSITION = OpVariable %_ptr_Input_v4float Input
+%in_var_COLOR = OpVariable %_ptr_Input_v4float Input
+%gl_Position = OpVariable %_ptr_Output_v4float Output
+%out_var_COLOR = OpVariable %_ptr_Output_v4float Output
+        %156 = OpExtInst %void %1 DebugInfoNone
+         %77 = OpExtInst %void %1 DebugExpression
+         %58 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 Float
+         %59 = OpExtInst %void %1 DebugTypeVector %58 4
+         %60 = OpExtInst %void %1 DebugSource %7
+         %61 = OpExtInst %void %1 DebugCompilationUnit 1 4 %60 HLSL
+         %62 = OpExtInst %void %1 DebugTypeComposite %9 Structure %60 1 8 %61 %9 %uint_256 FlagIsProtected|FlagIsPrivate %63 %64
+         %64 = OpExtInst %void %1 DebugTypeMember %10 %59 %60 3 10 %62 %uint_128 %uint_128 FlagIsProtected|FlagIsPrivate
+         %63 = OpExtInst %void %1 DebugTypeMember %11 %59 %60 2 10 %62 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
+         %65 = OpExtInst %void %1 DebugTypeBasic %12 %uint_32 Signed
+         %66 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %59 %65 %59
+         %67 = OpExtInst %void %1 DebugFunction %13 %66 %60 6 1 %61 %14 FlagIsProtected|FlagIsPrivate 6 %156
+         %68 = OpExtInst %void %1 DebugLexicalBlock %60 6 31 %67
+         %69 = OpExtInst %void %1 DebugLexicalBlock %60 7 21 %68
+         %70 = OpExtInst %void %1 DebugLocalVariable %11 %59 %60 6 26 %67 FlagIsLocal 2
+
+; 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
+         %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
+         %74 = OpExtInst %void %1 DebugLexicalBlock %60 15 38 %73
+         %75 = OpExtInst %void %1 DebugLocalVariable %17 %62 %60 16 13 %74 FlagIsLocal
+         %76 = OpExtInst %void %1 DebugLocalVariable %10 %59 %60 15 23 %73 FlagIsLocal 2
+         %78 = OpExtInst %void %1 DebugLocalVariable %11 %59 %60 14 23 %73 FlagIsLocal 1
+        %155 = OpExtInst %void %1 DebugInlinedAt 17 %74
+       %main = OpFunction %void None %50
+         %79 = OpLabel
+        %168 = OpExtInst %void %1 DebugScope %74
+
+; CHECK: [[i:%\w+]] = OpVariable %_ptr_Function_int Function
+        %120 = OpVariable %_ptr_Function_int Function
+        %121 = OpVariable %_ptr_Function_v4float Function
+        %169 = OpExtInst %void %1 DebugNoScope
+%param_var_pos = OpVariable %_ptr_Function_v4float Function
+%param_var_color = OpVariable %_ptr_Function_v4float Function
+         %80 = OpLoad %v4float %in_var_POSITION
+               OpStore %param_var_pos %80
+         %81 = OpLoad %v4float %in_var_COLOR
+               OpStore %param_var_color %81
+        %170 = OpExtInst %void %1 DebugScope %73
+        %124 = OpExtInst %void %1 DebugDeclare %78 %param_var_pos %77
+        %125 = OpExtInst %void %1 DebugDeclare %76 %param_var_color %77
+        %171 = OpExtInst %void %1 DebugScope %74
+               OpLine %7 17 18
+
+; CHECK: OpStore {{%\w+}} %int_4
+; CHECK: DebugValue [[dbg_i]] %int_4 [[null_expr]]
+               OpStore %120 %int_4
+               OpStore %121 %80
+        %172 = OpExtInst %void %1 DebugScope %67 %155
+        %135 = OpExtInst %void %1 DebugDeclare %71 %120 %77
+        %136 = OpExtInst %void %1 DebugDeclare %70 %121 %77
+        %173 = OpExtInst %void %1 DebugScope %68 %155
+               OpLine %7 7 3
+               OpBranch %137
+        %174 = OpExtInst %void %1 DebugNoScope
+        %137 = OpLabel
+
+; CHECK: [[phi:%\w+]] = OpPhi %int %int_4
+; CHECK: DebugValue [[dbg_i]] [[phi]] [[null_expr]]
+        %175 = OpExtInst %void %1 DebugScope %68 %155
+               OpLine %7 7 10
+        %138 = OpLoad %int %120
+        %139 = OpConvertSToF %float %138
+               OpLine %7 7 14
+        %140 = OpAccessChain %_ptr_Function_float %121 %int_0
+        %141 = OpLoad %float %140
+               OpLine %7 7 12
+        %142 = OpFOrdLessThan %bool %139 %141
+               OpLine %7 7 3
+        %176 = OpExtInst %void %1 DebugNoScope
+               OpLoopMerge %153 %152 None
+               OpBranchConditional %142 %143 %153
+        %177 = OpExtInst %void %1 DebugNoScope
+        %143 = OpLabel
+        %178 = OpExtInst %void %1 DebugScope %69 %155
+               OpLine %7 8 11
+        %144 = OpAccessChain %_ptr_Function_float %121 %int_0
+        %145 = OpLoad %float %144
+               OpLine %7 8 19
+        %146 = OpLoad %int %120
+        %147 = OpConvertSToF %float %146
+               OpLine %7 8 17
+        %148 = OpFAdd %float %145 %147
+               OpLine %7 8 11
+        %149 = OpCompositeConstruct %v4float %148 %148 %148 %148
+               OpLine %7 8 5
+               OpStore %121 %149
+               OpLine %7 9 5
+        %151 = OpIAdd %int %146 %int_1
+               OpLine %7 9 7
+
+; CHECK: OpStore [[i]] [[value:%\w+]]
+; CHECK: DebugValue [[dbg_i]] [[value]] [[null_expr]]
+               OpStore %120 %151
+        %179 = OpExtInst %void %1 DebugScope %68 %155
+               OpLine %7 10 3
+               OpBranch %152
+        %180 = OpExtInst %void %1 DebugNoScope
+        %152 = OpLabel
+        %181 = OpExtInst %void %1 DebugScope %68 %155
+               OpBranch %137
+        %182 = OpExtInst %void %1 DebugNoScope
+        %153 = OpLabel
+        %183 = OpExtInst %void %1 DebugScope %68 %155
+               OpLine %7 11 10
+        %154 = OpLoad %v4float %121
+        %184 = OpExtInst %void %1 DebugScope %74
+        %167 = OpExtInst %void %1 DebugValue %75 %154 %77 %int_0
+        %166 = OpExtInst %void %1 DebugValue %75 %81 %77 %int_1
+               OpLine %7 19 10
+        %165 = OpCompositeConstruct %VS_OUTPUT %154 %81
+        %185 = OpExtInst %void %1 DebugNoScope
+         %83 = OpCompositeExtract %v4float %165 0
+               OpStore %gl_Position %83
+         %84 = OpCompositeExtract %v4float %165 1
+               OpStore %out_var_COLOR %84
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<SSARewritePass>(text, true);
+}
+
+TEST_F(LocalSSAElimTest, PartiallyKillDebugDeclare) {
+  // For a reference variable e.g., int i in the following example,
+  // we do not propagate DebugValue for a store or phi instruction
+  // out of the variable's scope. In that case, we should not remove
+  // DebugDeclare for the variable that we did not add its DebugValue.
+  //
+  // #version 140
+  //
+  // in vec4 BC;
+  // out float fo;
+  //
+  // int j;
+  // void main()
+  // {
+  //     float f = 0.0;
+  //     for (j=0; j<4; j++) {
+  //       int& i = j;
+  //       f = f + BC[i];
+  //     }
+  //     fo = f;
+  // }
+
+  const std::string text = R"(
+; CHECK: [[f_name:%\w+]] = OpString "f"
+; CHECK: [[i_name:%\w+]] = OpString "i"
+; CHECK: [[fn:%\w+]] = OpExtInst %void [[ext:%\d+]] DebugFunction
+; CHECK: [[bb:%\w+]] = OpExtInst %void [[ext]] DebugLexicalBlock
+; CHECK: [[dbg_f:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[f_name]] {{%\w+}} {{%\w+}} 0 0 [[fn]]
+; CHECK: [[dbg_i:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[i_name]] {{%\w+}} {{%\w+}} 0 0 [[bb]]
+
+; CHECK:      OpStore %f %float_0
+; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] %float_0
+; CHECK-NOT:  DebugDeclare [[dbg_f]]
+; CHECK:      OpExtInst %void [[ext]] DebugDeclare [[dbg_i]] %j
+
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+%ext = OpExtInstImport "OpenCL.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"
+%j_name = OpString "j"
+OpName %main "main"
+OpName %f "f"
+OpName %j "j"
+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_32 = OpConstant %uint 32
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Private_int = OpTypePointer Private %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
+%j = OpVariable %_ptr_Private_int Private
+%null_expr = OpExtInst %void %ext DebugExpression
+%src = OpExtInst %void %ext DebugSource %file_name
+%cu = OpExtInst %void %ext DebugCompilationUnit 1 4 %src HLSL
+%dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 Float
+%dbg_v4f = OpExtInst %void %ext DebugTypeVector %dbg_tf 4
+%main_ty = OpExtInst %void %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %dbg_v4f %dbg_v4f
+%dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 10 %main
+%bb = OpExtInst %void %ext DebugLexicalBlock %src 0 0 %dbg_main
+%dbg_f = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_v4f %src 0 0 %dbg_main FlagIsLocal
+%dbg_i = OpExtInst %void %ext DebugLocalVariable %i_name %dbg_v4f %src 0 0 %bb FlagIsLocal
+%dbg_j = OpExtInst %void %ext DebugGlobalVariable %j_name %dbg_v4f %src 0 0 %dbg_main %j_name %j FlagIsPrivate
+%main = OpFunction %void None %8
+%22 = OpLabel
+%s0 = OpExtInst %void %ext DebugScope %dbg_main
+%f = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpStore %j %int_0
+%decl0 = OpExtInst %void %ext DebugDeclare %dbg_f %f %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 %j
+%28 = OpSLessThan %bool %27 %int_4
+OpBranchConditional %28 %29 %24
+%29 = OpLabel
+%s3 = OpExtInst %void %ext DebugScope %bb
+%decl1 = OpExtInst %void %ext DebugDeclare %dbg_i %j %null_expr
+%30 = OpLoad %float %f
+%31 = OpLoad %int %j
+%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 %j
+%36 = OpIAdd %int %35 %int_1
+OpStore %j %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, DebugValueForReferenceVariable) {
   // #version 140
   //
@@ -2357,8 +2692,9 @@
 ; CHECK:      OpExtInst %void [[ext]] DebugScope [[dbg_main]]
 ; 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-DAG: OpExtInst %void [[ext]] DebugValue [[dbg_f]] [[phi0]]
+; CHECK-DAG: OpExtInst %void [[ext]] DebugValue [[dbg_x]] [[phi0]]
+; CHECK-DAG: OpExtInst %void [[ext]] DebugValue [[dbg_i]] [[phi1]]
 ; CHECK:      OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]] None
 ; CHECK-NEXT: OpBranch [[loop_body:%\w+]]
 
@@ -3362,6 +3698,199 @@
   SinglePassRunAndMatch<SSARewritePass>(text, true);
 }
 
+TEST_F(LocalSSAElimTest, RemoveDebugDeclareWithoutLoads) {
+  // Check that the DebugDeclare for c is removed even though its loads
+  // had been removed previously by single block store/load optimization.
+  // In the presence of DebugDeclare, single-block can and does remove loads,
+  // but cannot change the stores into DebugValues and remove the DebugDeclare
+  // because it is only a per block optimization, not a function optimization.
+  // So SSA-rewrite must perform this role.
+  //
+  // Texture2D g_tColor;
+  // SamplerState g_sAniso;
+  //
+  // struct PS_INPUT
+  // {
+  //   float2 vTextureCoords2 : TEXCOORD2;
+  //   float2 vTextureCoords3 : TEXCOORD3;
+  // };
+  //
+  // struct PS_OUTPUT
+  // {
+  //   float4 vColor : SV_Target0;
+  // };
+  //
+  // PS_OUTPUT MainPs(PS_INPUT i)
+  // {
+  //   PS_OUTPUT ps_output;
+  //   float4 c;
+  //   c = g_tColor.Sample(g_sAniso, i.vTextureCoords2.xy);
+  //   c += g_tColor.Sample(g_sAniso, i.vTextureCoords3.xy);
+  //   ps_output.vColor = c;
+  //   return ps_output;
+  // }
+
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %g_sAniso %in_var_TEXCOORD2 %in_var_TEXCOORD3 %out_var_SV_Target0
+               OpExecutionMode %MainPs OriginUpperLeft
+         %22 = OpString "foo.frag"
+         %26 = OpString "PS_OUTPUT"
+         %30 = OpString "float"
+         %33 = OpString "vColor"
+         %35 = OpString "PS_INPUT"
+         %40 = OpString "vTextureCoords3"
+         %42 = OpString "vTextureCoords2"
+         %44 = OpString "@type.2d.image"
+         %45 = OpString "type.2d.image"
+         %47 = OpString "Texture2D.TemplateParam"
+         %51 = OpString "src.MainPs"
+         %55 = OpString "c"
+         %57 = OpString "ps_output"
+         %60 = OpString "i"
+         %62 = OpString "@type.sampler"
+         %63 = OpString "type.sampler"
+         %65 = OpString "g_sAniso"
+         %67 = 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 %in_var_TEXCOORD3 "in.var.TEXCOORD3"
+               OpName %out_var_SV_Target0 "out.var.SV_Target0"
+               OpName %MainPs "MainPs"
+               OpName %PS_INPUT "PS_INPUT"
+               OpMemberName %PS_INPUT 0 "vTextureCoords2"
+               OpMemberName %PS_INPUT 1 "vTextureCoords3"
+               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 %in_var_TEXCOORD3 Location 1
+               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
+      %int_1 = OpConstant %int 1
+       %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_64 = OpConstant %uint 64
+         %69 = OpTypeFunction %void
+   %PS_INPUT = OpTypeStruct %v2float %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+  %PS_OUTPUT = OpTypeStruct %v4float
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+   %g_tColor = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+   %g_sAniso = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%in_var_TEXCOORD2 = OpVariable %_ptr_Input_v2float Input
+%in_var_TEXCOORD3 = OpVariable %_ptr_Input_v2float Input
+%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output
+         %43 = OpExtInst %void %1 DebugInfoNone
+         %59 = OpExtInst %void %1 DebugExpression
+         %24 = OpExtInst %void %1 DebugSource %22
+         %25 = OpExtInst %void %1 DebugCompilationUnit 1 4 %24 HLSL
+         %28 = OpExtInst %void %1 DebugTypeComposite %26 Structure %24 11 1 %25 %26 %uint_128 FlagIsProtected|FlagIsPrivate %29
+         %31 = OpExtInst %void %1 DebugTypeBasic %30 %uint_32 Float
+         %32 = OpExtInst %void %1 DebugTypeVector %31 4
+         %29 = OpExtInst %void %1 DebugTypeMember %33 %32 %24 13 5 %28 %uint_0 %uint_128 FlagIsProtected|FlagIsPrivate
+         %36 = OpExtInst %void %1 DebugTypeComposite %35 Structure %24 5 1 %25 %35 %uint_128 FlagIsProtected|FlagIsPrivate %37 %38
+         %39 = OpExtInst %void %1 DebugTypeVector %31 2
+         %38 = OpExtInst %void %1 DebugTypeMember %40 %39 %24 8 5 %36 %uint_64 %uint_64 FlagIsProtected|FlagIsPrivate
+         %37 = OpExtInst %void %1 DebugTypeMember %42 %39 %24 7 5 %36 %uint_0 %uint_64 FlagIsProtected|FlagIsPrivate
+         %46 = OpExtInst %void %1 DebugTypeComposite %44 Class %24 0 0 %25 %45 %43 FlagIsProtected|FlagIsPrivate
+         %50 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %28 %36
+         %52 = OpExtInst %void %1 DebugFunction %51 %50 %24 16 1 %25 %51 FlagIsProtected|FlagIsPrivate 17 %43
+         %54 = OpExtInst %void %1 DebugLexicalBlock %24 17 1 %52
+         %56 = OpExtInst %void %1 DebugLocalVariable %55 %32 %24 20 12 %54 FlagIsLocal
+         %58 = OpExtInst %void %1 DebugLocalVariable %57 %28 %24 18 15 %54 FlagIsLocal
+         %61 = OpExtInst %void %1 DebugLocalVariable %60 %36 %24 16 29 %52 FlagIsLocal 1
+         %64 = OpExtInst %void %1 DebugTypeComposite %62 Structure %24 0 0 %25 %63 %43 FlagIsProtected|FlagIsPrivate
+         %66 = OpExtInst %void %1 DebugGlobalVariable %65 %64 %24 3 14 %25 %65 %g_sAniso FlagIsDefinition
+         %68 = OpExtInst %void %1 DebugGlobalVariable %67 %46 %24 1 11 %25 %67 %g_tColor FlagIsDefinition
+     %MainPs = OpFunction %void None %69
+         %70 = OpLabel
+        %135 = OpExtInst %void %1 DebugScope %54
+        %111 = OpVariable %_ptr_Function_PS_OUTPUT Function
+        %112 = OpVariable %_ptr_Function_v4float Function
+        %136 = OpExtInst %void %1 DebugNoScope
+%param_var_i = OpVariable %_ptr_Function_PS_INPUT Function
+         %74 = OpLoad %v2float %in_var_TEXCOORD2
+         %75 = OpLoad %v2float %in_var_TEXCOORD3
+         %76 = OpCompositeConstruct %PS_INPUT %74 %75
+               OpStore %param_var_i %76
+        %137 = OpExtInst %void %1 DebugScope %52
+        %115 = OpExtInst %void %1 DebugDeclare %61 %param_var_i %59
+        %138 = OpExtInst %void %1 DebugScope %54
+        %116 = OpExtInst %void %1 DebugDeclare %58 %111 %59
+        %117 = OpExtInst %void %1 DebugDeclare %56 %112 %59
+;CHECK-NOT: %117 = OpExtInst %void %1 DebugDeclare %56 %112 %59
+               OpLine %22 21 9
+        %118 = OpLoad %type_2d_image %g_tColor
+               OpLine %22 21 29
+        %119 = OpLoad %type_sampler %g_sAniso
+               OpLine %22 21 40
+        %120 = OpAccessChain %_ptr_Function_v2float %param_var_i %int_0
+        %121 = OpLoad %v2float %120
+               OpLine %22 21 9
+        %122 = OpSampledImage %type_sampled_image %118 %119
+        %123 = OpImageSampleImplicitLod %v4float %122 %121 None
+               OpLine %22 21 5
+               OpStore %112 %123
+;CHECK: %140 = OpExtInst %void %1 DebugValue %56 %123 %59
+               OpLine %22 22 10
+        %124 = OpLoad %type_2d_image %g_tColor
+               OpLine %22 22 30
+        %125 = OpLoad %type_sampler %g_sAniso
+               OpLine %22 22 41
+        %126 = OpAccessChain %_ptr_Function_v2float %param_var_i %int_1
+        %127 = OpLoad %v2float %126
+               OpLine %22 22 10
+        %128 = OpSampledImage %type_sampled_image %124 %125
+        %129 = OpImageSampleImplicitLod %v4float %128 %127 None
+               OpLine %22 22 7
+        %131 = OpFAdd %v4float %123 %129
+               OpLine %22 22 5
+               OpStore %112 %131
+;CHECK: %141 = OpExtInst %void %1 DebugValue %56 %131 %59
+               OpLine %22 23 5
+        %133 = OpAccessChain %_ptr_Function_v4float %111 %int_0
+               OpStore %133 %131
+               OpLine %22 24 12
+        %134 = OpLoad %PS_OUTPUT %111
+        %139 = OpExtInst %void %1 DebugNoScope
+         %79 = OpCompositeExtract %v4float %134 0
+               OpStore %out_var_SV_Target0 %79
+               OpReturn
+               OpFunctionEnd
+)";
+
+  SetTargetEnv(SPV_ENV_VULKAN_1_2);
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndMatch<SSARewritePass>(text, true);
+}
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    No optimization in the presence of
diff --git a/test/opt/loop_optimizations/unroll_simple.cpp b/test/opt/loop_optimizations/unroll_simple.cpp
index 6030135..016316a 100644
--- a/test/opt/loop_optimizations/unroll_simple.cpp
+++ b/test/opt/loop_optimizations/unroll_simple.cpp
@@ -194,6 +194,190 @@
   SinglePassRunAndCheck<LoopUnroller>(text, output, false);
 }
 
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+  float x[4];
+  for (int i = 0; i < 4; ++i) {
+    x[i] = 1.0f;
+  }
+}
+*/
+TEST_F(PassClassTest, SimpleFullyUnrollWithDebugInstructions) {
+  // We must preserve the debug information including OpenCL.DebugInfo.100
+  // instructions and OpLine instructions. Only the first block has
+  // DebugDeclare and DebugValue used for the declaration (i.e., DebugValue
+  // with Deref). Other blocks unrolled from the loop must not contain them.
+  const std::string text = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+%ext = OpExtInstImport "OpenCL.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_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 Deref
+%deref_expr = OpExtInst %6 %ext DebugExpression %deref
+%src = OpExtInst %6 %ext DebugSource %file_name
+%cu = OpExtInst %6 %ext DebugCompilationUnit 1 4 %src HLSL
+%dbg_tf = OpExtInst %6 %ext DebugTypeBasic %float_name %uint_32 Float
+%dbg_v4f = OpExtInst %6 %ext DebugTypeVector %dbg_tf 4
+%main_ty = OpExtInst %6 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %dbg_v4f %dbg_v4f
+%dbg_main = OpExtInst %6 %ext DebugFunction %main_name %main_ty %src 0 0 %cu %main_name FlagIsProtected|FlagIsPrivate 10 %2
+%bb = OpExtInst %6 %ext DebugLexicalBlock %src 0 0 %dbg_main
+%dbg_f = OpExtInst %6 %ext DebugLocalVariable %f_name %dbg_v4f %src 0 0 %dbg_main FlagIsLocal
+%dbg_i = OpExtInst %6 %ext DebugLocalVariable %i_name %dbg_v4f %src 1 0 %bb FlagIsLocal
+
+; 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 Deref
+; 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+}} 0 0 [[dbg_fn]]
+; CHECK: [[dbg_i:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable [[i]] {{%\w+}} {{%\w+}} 1 0 [[dbg_bb]]
+
+%2 = OpFunction %6 None %7
+%23 = OpLabel
+
+; The first block has DebugDeclare and DebugValue with Deref
+;
+; CHECK: OpLabel
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: [[x:%\w+]] = OpVariable {{%\w+}} Function
+; CHECK: OpLine {{%\w+}} 0 0
+; CHECK: OpBranch
+; CHECK: OpLabel
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: DebugValue [[dbg_f]] [[int_0]] [[null_expr]]
+; CHECK: OpBranch
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: OpLine {{%\w+}} 1 1
+; CHECK: OpSLessThan
+; CHECK: OpLine {{%\w+}} 2 0
+; CHECK: OpBranch
+; CHECK: OpLabel
+; CHECK: DebugScope [[dbg_bb]]
+; CHECK: DebugDeclare [[dbg_f]] [[x]] [[null_expr]]
+; CHECK: DebugValue [[dbg_i]] [[x]] [[deref_expr]]
+; CHECK: OpLine {{%\w+}} 3 0
+;
+; CHECK: OpLine {{%\w+}} 6 0
+; CHECK: [[add:%\w+]] = OpIAdd
+; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]]
+; CHECK: OpLine {{%\w+}} 7 0
+
+; Other blocks do not have DebugDeclare and DebugValue with Deref
+;
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: OpLine {{%\w+}} 1 1
+; CHECK: OpSLessThan
+; CHECK: OpLine {{%\w+}} 2 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: OpLine {{%\w+}} 3 0
+;
+; CHECK: OpLine {{%\w+}} 6 0
+; CHECK: [[add:%\w+]] = OpIAdd
+; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]]
+; CHECK: OpLine {{%\w+}} 7 0
+;
+; CHECK-NOT: DebugDeclare [[dbg_f]] [[x]] [[null_expr]]
+; CHECK-NOT: DebugValue [[dbg_i]] [[x]] [[deref_expr]]
+; CHECK: DebugScope [[dbg_fn]]
+; CHECK: OpLine {{%\w+}} 8 0
+; CHECK: OpReturn
+
+%s0 = OpExtInst %6 %ext DebugScope %dbg_main
+%5 = OpVariable %17 Function
+OpLine %file_name 0 0
+OpBranch %24
+%24 = OpLabel
+%s1 = OpExtInst %6 %ext DebugScope %dbg_main
+%35 = OpPhi %8 %10 %23 %34 %26
+%value0 = OpExtInst %6 %ext DebugValue %dbg_f %35 %null_expr
+OpLine %file_name 1 0
+OpLoopMerge %25 %26 Unroll
+OpBranch %27
+%27 = OpLabel
+%s2 = OpExtInst %6 %ext DebugScope %dbg_main
+OpLine %file_name 1 1
+%29 = OpSLessThan %12 %35 %11
+OpLine %file_name 2 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
+OpLine %file_name 3 0
+%32 = OpAccessChain %19 %5 %35
+OpLine %file_name 4 0
+OpStore %32 %18
+OpLine %file_name 5 0
+OpBranch %26
+%26 = OpLabel
+%s4 = OpExtInst %6 %ext DebugScope %dbg_main
+OpLine %file_name 6 0
+%34 = OpIAdd %8 %35 %20
+%value1 = OpExtInst %6 %ext DebugValue %dbg_f %34 %null_expr
+OpLine %file_name 7 0
+OpBranch %24
+%25 = OpLabel
+%s5 = OpExtInst %6 %ext DebugScope %dbg_main
+OpLine %file_name 8 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);
+  SinglePassRunAndMatch<LoopUnroller>(text, true);
+}
+
 template <int factor>
 class PartialUnrollerTestPass : public Pass {
  public:
@@ -3068,6 +3252,155 @@
   SinglePassRunAndMatch<opt::LoopUnroller>(text, true, kFullyUnroll,
                                            kUnrollFactor);
 }
+
+TEST_F(PassClassTest, InitValueIsConstantNull) {
+  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
+          %7 = OpConstantNull %6
+         %13 = OpConstant %6 1
+         %21 = OpConstant %6 1
+         %10 = OpTypeBool
+         %17 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+         %23 = OpPhi %6 %7 %11 %20 %15
+               OpLoopMerge %8 %15 Unroll
+               OpBranch %14
+         %14 = OpLabel
+          %9 = OpSLessThan %10 %23 %13
+               OpBranchConditional %9 %15 %8
+         %15 = OpLabel
+         %20 = OpIAdd %6 %23 %21
+               OpBranch %5
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource ESSL 320
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpConstantNull %5
+%7 = OpConstant %5 1
+%8 = OpConstant %5 1
+%9 = OpTypeBool
+%10 = OpTypePointer Function %5
+%2 = OpFunction %3 None %4
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpSLessThan %9 %6 %7
+OpBranch %15
+%15 = OpLabel
+%14 = OpIAdd %5 %6 %8
+OpBranch %16
+%16 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, shader,
+                             SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << shader << std::endl;
+
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  SinglePassRunAndCheck<LoopUnroller>(shader, output, false);
+}
+
+TEST_F(PassClassTest, ConditionValueIsConstantNull) {
+  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
+          %7 = OpConstantNull %6
+         %13 = OpConstant %6 1
+         %21 = OpConstant %6 1
+         %10 = OpTypeBool
+         %17 = OpTypePointer Function %6
+          %4 = OpFunction %2 None %3
+         %11 = OpLabel
+               OpBranch %5
+          %5 = OpLabel
+         %23 = OpPhi %6 %13 %11 %20 %15
+               OpLoopMerge %8 %15 Unroll
+               OpBranch %14
+         %14 = OpLabel
+          %9 = OpSGreaterThan %10 %23 %7
+               OpBranchConditional %9 %15 %8
+         %15 = OpLabel
+         %20 = OpISub %6 %23 %21
+               OpBranch %5
+          %8 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource ESSL 320
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpConstantNull %5
+%7 = OpConstant %5 1
+%8 = OpConstant %5 1
+%9 = OpTypeBool
+%10 = OpTypePointer Function %5
+%2 = OpFunction %3 None %4
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpSGreaterThan %9 %7 %6
+OpBranch %15
+%15 = OpLabel
+%14 = OpISub %5 %7 %8
+OpBranch %16
+%16 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, shader,
+                             SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  Module* module = context->module();
+  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+                             << shader << std::endl;
+
+  SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+  SinglePassRunAndCheck<LoopUnroller>(shader, output, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/module_test.cpp b/test/opt/module_test.cpp
index 406da09..a3c2eed 100644
--- a/test/opt/module_test.cpp
+++ b/test/opt/module_test.cpp
@@ -295,6 +295,47 @@
 
   AssembleAndDisassemble(text);
 }
+
+TEST(ModuleTest, NonSemanticInfoIteration) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%4 = OpExtInst %2 %1 1
+%5 = OpFunction %2 None %3
+%6 = OpLabel
+%7 = OpExtInst %2 %1 1
+OpReturn
+OpFunctionEnd
+%8 = OpExtInst %2 %1 1
+%9 = OpFunction %2 None %3
+%10 = OpLabel
+%11 = OpExtInst %2 %1 1
+OpReturn
+OpFunctionEnd
+%12 = OpExtInst %2 %1 1
+)";
+
+  std::unique_ptr<IRContext> context = BuildModule(text);
+  std::unordered_set<uint32_t> non_semantic_ids;
+  context->module()->ForEachInst(
+      [&non_semantic_ids](const Instruction* inst) {
+        if (inst->opcode() == SpvOpExtInst) {
+          non_semantic_ids.insert(inst->result_id());
+        }
+      },
+      false);
+
+  EXPECT_EQ(1, non_semantic_ids.count(4));
+  EXPECT_EQ(1, non_semantic_ids.count(7));
+  EXPECT_EQ(1, non_semantic_ids.count(8));
+  EXPECT_EQ(1, non_semantic_ids.count(11));
+  EXPECT_EQ(1, non_semantic_ids.count(12));
+}
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index 64c089d..e520821 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -176,9 +176,11 @@
   // result, using checks parsed from |original|.  Always skips OpNop.
   // This does *not* involve pass manager.  Callers are suggested to use
   // SCOPED_TRACE() for better messages.
+  // Returns a tuple of disassembly string and the boolean value from the pass
+  // Process() function.
   template <typename PassT, typename... Args>
-  void SinglePassRunAndMatch(const std::string& original, bool do_validation,
-                             Args&&... args) {
+  std::tuple<std::string, Pass::Status> SinglePassRunAndMatch(
+      const std::string& original, bool do_validation, Args&&... args) {
     const bool skip_nop = true;
     auto pass_result = SinglePassRunAndDisassemble<PassT>(
         original, skip_nop, do_validation, std::forward<Args>(args)...);
@@ -187,6 +189,7 @@
     EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
         << match_result.message() << "\nChecking result:\n"
         << disassembly;
+    return pass_result;
   }
 
   // Runs a single pass of class |PassT| on the binary assembled from the
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
index f426904..fd97efa 100644
--- a/test/opt/pass_merge_return_test.cpp
+++ b/test/opt/pass_merge_return_test.cpp
@@ -534,7 +534,7 @@
 ; CHECK: [[true:%\w+]] = OpConstantTrue
 ; CHECK: OpFunction
 ; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
-; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]]
 ; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
 ; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
 ; CHECK: [[if_lab]] = OpLabel
@@ -542,9 +542,9 @@
 ; CHECK-NEXT: OpBranch
 ; CHECK: [[then_lab]] = OpLabel
 ; CHECK-NEXT: OpStore [[var]] [[true]]
-; CHECK-NEXT: OpBranch [[dummy_loop_merge]]
+; CHECK-NEXT: OpBranch [[single_case_switch_merge]]
 ; CHECK: [[merge_lab]] = OpLabel
-; CHECK: [[dummy_loop_merge]] = OpLabel
+; CHECK: [[single_case_switch_merge]] = OpLabel
 ; CHECK-NEXT: OpReturn
 OpCapability Addresses
 OpCapability Shader
@@ -631,10 +631,10 @@
   const std::string before =
       R"(
 ; CHECK: OpFunction
-; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]]
 ; CHECK: OpLoopMerge [[loop_merge:%\w+]]
 ; CHECK: [[loop_merge]] = OpLabel
-; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]] [[old_code_path:%\w+]]
+; CHECK: OpBranchConditional {{%\w+}} [[single_case_switch_merge]] [[old_code_path:%\w+]]
 ; CHECK: [[old_code_path:%\w+]] = OpLabel
 ; CHECK: OpBranchConditional {{%\w+}} [[side_node:%\w+]] [[phi_block:%\w+]]
 ; CHECK: [[phi_block]] = OpLabel
@@ -828,7 +828,7 @@
       R"(
 ; CHECK: OpFunction
 ; CHECK: [[ret_flag:%\w+]] = OpVariable %_ptr_Function_bool Function %false
-; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]]
 ; CHECK: OpLoopMerge [[loop1_merge:%\w+]] {{%\w+}}
 ; CHECK-NEXT: OpBranchConditional {{%\w+}} [[if_lab:%\w+]] {{%\w+}}
 ; CHECK: [[if_lab]] = OpLabel
@@ -837,7 +837,7 @@
 ; CHECK: [[loop1_merge]] = OpLabel
 ; CHECK-NEXT: [[ld:%\w+]] = OpLoad %bool [[ret_flag]]
 ; CHECK-NOT: OpLabel
-; CHECK: OpBranchConditional [[ld]] [[dummy_loop_merge]] [[empty_block:%\w+]]
+; CHECK: OpBranchConditional [[ld]] [[single_case_switch_merge]] [[empty_block:%\w+]]
 ; CHECK: [[empty_block]] = OpLabel
 ; CHECK-NEXT: OpBranch [[loop2:%\w+]]
 ; CHECK: [[loop2]] = OpLabel
@@ -1217,7 +1217,7 @@
   const std::string test =
       R"(
 ; CHECK: OpFunction
-; CHECK: OpSelectionMerge [[dummy_loop_merge:%\w+]]
+; CHECK: OpSelectionMerge [[single_case_switch_merge:%\w+]]
 ; CHECK: OpLoopMerge [[outer_loop_merge:%\w+]]
 ; CHECK: OpLoopMerge [[inner_loop_merge:%\w+]]
 ; CHECK: OpSelectionMerge
@@ -1230,8 +1230,8 @@
 ; CHECK: OpBranchConditional {{%\w+}} [[outer_loop_merge]]
 ; CHECK: [[outer_loop_merge]] = OpLabel
 ; CHECK-NOT: OpLabel
-; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]]
-; CHECK: [[dummy_loop_merge]] = OpLabel
+; CHECK: OpBranchConditional {{%\w+}} [[single_case_switch_merge]]
+; CHECK: [[single_case_switch_merge]] = OpLabel
 ; CHECK-NOT: OpLabel
 ; CHECK: OpReturn
                OpCapability SampledBuffer
@@ -2145,12 +2145,12 @@
 }
 
 TEST_F(MergeReturnPassTest, ReturnsInSwitch) {
-  //  Cannot branch directly to dummy switch merge block from original switch.
-  //  Must branch to merge block of original switch and then do predicated
-  //  branch to merge block of dummy switch.
+  //  Cannot branch directly to single case switch merge block from original
+  //  switch. Must branch to merge block of original switch and then do
+  //  predicated branch to merge block of single case switch.
   const std::string text =
       R"(
-; CHECK: OpSelectionMerge [[dummy_merge_bb:%\w+]]
+; CHECK: OpSelectionMerge [[single_case_switch_merge_bb:%\w+]]
 ; CHECK-NEXT: OpSwitch {{%\w+}} [[def_bb1:%\w+]]
 ; CHECK-NEXT: [[def_bb1]] = OpLabel
 ; CHECK: OpSelectionMerge
@@ -2158,7 +2158,7 @@
 ; CHECK: OpBranch [[inner_merge_bb]]
 ; CHECK: OpBranch [[inner_merge_bb]]
 ; CHECK-NEXT: [[inner_merge_bb]] = OpLabel
-; CHECK: OpBranchConditional {{%\w+}} [[dummy_merge_bb]] {{%\w+}}
+; CHECK: OpBranchConditional {{%\w+}} [[single_case_switch_merge_bb]] {{%\w+}}
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
diff --git a/test/opt/process_lines_test.cpp b/test/opt/process_lines_test.cpp
deleted file mode 100644
index 33ad4be..0000000
--- a/test/opt/process_lines_test.cpp
+++ /dev/null
@@ -1,695 +0,0 @@
-// Copyright (c) 2017 Valve Corporation
-// Copyright (c) 2017 LunarG Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "test/opt/pass_fixture.h"
-#include "test/opt/pass_utils.h"
-
-namespace spvtools {
-namespace opt {
-namespace {
-
-using ProcessLinesTest = PassTest<::testing::Test>;
-
-TEST_F(ProcessLinesTest, SimplePropagation) {
-  // Texture2D g_tColor[128];
-  //
-  // layout(push_constant) cbuffer PerViewConstantBuffer_t
-  // {
-  //   uint g_nDataIdx;
-  //   uint g_nDataIdx2;
-  //   bool g_B;
-  // };
-  //
-  // SamplerState g_sAniso;
-  //
-  // struct PS_INPUT
-  // {
-  //   float2 vTextureCoords : TEXCOORD2;
-  // };
-  //
-  // struct PS_OUTPUT
-  // {
-  //   float4 vColor : SV_Target0;
-  // };
-  //
-  // PS_OUTPUT MainPs(PS_INPUT i)
-  // {
-  //   PS_OUTPUT ps_output;
-  //
-  //   uint u;
-  //   if (g_B)
-  //     u = g_nDataIdx;
-  //   else
-  //     u = g_nDataIdx2;
-  //   ps_output.vColor = g_tColor[u].Sample(g_sAniso, i.vTextureCoords.xy);
-  //   return ps_output;
-  // }
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
-OpExecutionMode %MainPs OriginUpperLeft
-%5 = OpString "foo.frag"
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %PS_INPUT "PS_INPUT"
-OpMemberName %PS_INPUT 0 "vTextureCoords"
-OpName %PS_OUTPUT "PS_OUTPUT"
-OpMemberName %PS_OUTPUT 0 "vColor"
-OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
-OpName %i "i"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpMemberName %PerViewConstantBuffer_t 1 "g_nDataIdx2"
-OpMemberName %PerViewConstantBuffer_t 2 "g_B"
-OpName %_ ""
-OpName %u "u"
-OpName %ps_output "ps_output"
-OpName %g_tColor "g_tColor"
-OpName %g_sAniso "g_sAniso"
-OpName %i_0 "i"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpName %param "param"
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
-OpMemberDecorate %PerViewConstantBuffer_t 2 Offset 8
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_tColor DescriptorSet 0
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-)";
-
-  const std::string before =
-      R"(%void = OpTypeVoid
-%19 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%PS_INPUT = OpTypeStruct %v2float
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-%v4float = OpTypeVector %float 4
-%PS_OUTPUT = OpTypeStruct %v4float
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-%uint = OpTypeInt 32 0
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%int = OpTypeInt 32 1
-%int_2 = OpConstant %int 2
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%bool = OpTypeBool
-%uint_0 = OpConstant %uint 0
-%_ptr_Function_uint = OpTypePointer Function %uint
-%int_0 = OpConstant %int 0
-%int_1 = OpConstant %int 1
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint_128 = OpConstant %uint 128
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-%41 = OpTypeSampler
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-%43 = OpTypeSampledImage %36
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%MainPs = OpFunction %void None %19
-%48 = OpLabel
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpStore %50 %49
-%51 = OpLoad %PS_INPUT %i_0
-OpStore %param %51
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-%53 = OpCompositeExtract %v4float %52 0
-OpStore %_entryPointOutput_vColor %53
-OpReturn
-OpFunctionEnd
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-%54 = OpLabel
-%u = OpVariable %_ptr_Function_uint Function
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-%56 = OpLoad %uint %55
-%57 = OpINotEqual %bool %56 %uint_0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%62 = OpLoad %uint %61
-OpStore %u %62
-OpBranch %58
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-%64 = OpLoad %uint %63
-OpStore %u %64
-OpBranch %58
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-%67 = OpLoad %36 %66
-%68 = OpLoad %41 %g_sAniso
-%69 = OpSampledImage %43 %67 %68
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-%71 = OpLoad %v2float %70
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpReturnValue %74
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(OpNoLine
-%void = OpTypeVoid
-OpNoLine
-%19 = OpTypeFunction %void
-OpNoLine
-%float = OpTypeFloat 32
-OpNoLine
-%v2float = OpTypeVector %float 2
-OpNoLine
-%PS_INPUT = OpTypeStruct %v2float
-OpNoLine
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-OpNoLine
-%v4float = OpTypeVector %float 4
-OpNoLine
-%PS_OUTPUT = OpTypeStruct %v4float
-OpNoLine
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-OpNoLine
-%uint = OpTypeInt 32 0
-OpNoLine
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-OpNoLine
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-OpNoLine
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-OpNoLine
-%int = OpTypeInt 32 1
-OpNoLine
-%int_2 = OpConstant %int 2
-OpNoLine
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-OpNoLine
-%bool = OpTypeBool
-OpNoLine
-%uint_0 = OpConstant %uint 0
-OpNoLine
-%_ptr_Function_uint = OpTypePointer Function %uint
-OpNoLine
-%int_0 = OpConstant %int 0
-OpNoLine
-%int_1 = OpConstant %int 1
-OpNoLine
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-OpNoLine
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-OpNoLine
-%uint_128 = OpConstant %uint 128
-OpNoLine
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-OpNoLine
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-OpNoLine
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-OpNoLine
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-OpNoLine
-%41 = OpTypeSampler
-OpNoLine
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-OpNoLine
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-OpNoLine
-%43 = OpTypeSampledImage %36
-OpNoLine
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-OpNoLine
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-OpNoLine
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-OpNoLine
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-OpNoLine
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-OpNoLine
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-OpNoLine
-%MainPs = OpFunction %void None %19
-OpNoLine
-%48 = OpLabel
-OpNoLine
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-OpNoLine
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-OpLine %5 23 0
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpLine %5 23 0
-OpStore %50 %49
-OpLine %5 23 0
-%51 = OpLoad %PS_INPUT %i_0
-OpLine %5 23 0
-OpStore %param %51
-OpLine %5 23 0
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-OpLine %5 23 0
-%53 = OpCompositeExtract %v4float %52 0
-OpLine %5 23 0
-OpStore %_entryPointOutput_vColor %53
-OpLine %5 23 0
-OpReturn
-OpNoLine
-OpFunctionEnd
-OpNoLine
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-OpNoLine
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-OpNoLine
-%54 = OpLabel
-OpNoLine
-%u = OpVariable %_ptr_Function_uint Function
-OpNoLine
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-OpLine %5 27 0
-%56 = OpLoad %uint %55
-OpLine %5 27 0
-%57 = OpINotEqual %bool %56 %uint_0
-OpLine %5 27 0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-OpNoLine
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-OpLine %5 28 0
-%62 = OpLoad %uint %61
-OpLine %5 28 0
-OpStore %u %62
-OpLine %5 28 0
-OpBranch %58
-OpNoLine
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-OpLine %5 30 0
-%64 = OpLoad %uint %63
-OpLine %5 30 0
-OpStore %u %64
-OpLine %5 30 0
-OpBranch %58
-OpNoLine
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-OpLine %5 31 0
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-OpLine %5 31 0
-%67 = OpLoad %36 %66
-OpLine %5 31 0
-%68 = OpLoad %41 %g_sAniso
-OpLine %5 31 0
-%69 = OpSampledImage %43 %67 %68
-OpLine %5 31 0
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-OpLine %5 31 0
-%71 = OpLoad %v2float %70
-OpLine %5 31 0
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-OpLine %5 31 0
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpLine %5 31 0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpLine %5 32 0
-OpReturnValue %74
-OpNoLine
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<ProcessLinesPass>(predefs + before, predefs + after,
-                                          false, true, kLinesPropagateLines);
-}
-
-TEST_F(ProcessLinesTest, SimpleElimination) {
-  // Previous test with before and after reversed
-
-  const std::string predefs =
-      R"(OpCapability Shader
-%1 = OpExtInstImport "GLSL.std.450"
-OpMemoryModel Logical GLSL450
-OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
-OpExecutionMode %MainPs OriginUpperLeft
-%5 = OpString "foo.frag"
-OpSource HLSL 500
-OpName %MainPs "MainPs"
-OpName %PS_INPUT "PS_INPUT"
-OpMemberName %PS_INPUT 0 "vTextureCoords"
-OpName %PS_OUTPUT "PS_OUTPUT"
-OpMemberName %PS_OUTPUT 0 "vColor"
-OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
-OpName %i "i"
-OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
-OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
-OpMemberName %PerViewConstantBuffer_t 1 "g_nDataIdx2"
-OpMemberName %PerViewConstantBuffer_t 2 "g_B"
-OpName %_ ""
-OpName %u "u"
-OpName %ps_output "ps_output"
-OpName %g_tColor "g_tColor"
-OpName %g_sAniso "g_sAniso"
-OpName %i_0 "i"
-OpName %i_vTextureCoords "i.vTextureCoords"
-OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
-OpName %param "param"
-OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
-OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
-OpMemberDecorate %PerViewConstantBuffer_t 2 Offset 8
-OpDecorate %PerViewConstantBuffer_t Block
-OpDecorate %g_tColor DescriptorSet 0
-OpDecorate %g_sAniso DescriptorSet 0
-OpDecorate %i_vTextureCoords Location 0
-OpDecorate %_entryPointOutput_vColor Location 0
-)";
-
-  const std::string before =
-      R"(OpNoLine
-%void = OpTypeVoid
-OpNoLine
-%19 = OpTypeFunction %void
-OpNoLine
-%float = OpTypeFloat 32
-OpNoLine
-%v2float = OpTypeVector %float 2
-OpNoLine
-%PS_INPUT = OpTypeStruct %v2float
-OpNoLine
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-OpNoLine
-%v4float = OpTypeVector %float 4
-OpNoLine
-%PS_OUTPUT = OpTypeStruct %v4float
-OpNoLine
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-OpNoLine
-%uint = OpTypeInt 32 0
-OpNoLine
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-OpNoLine
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-OpNoLine
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-OpNoLine
-%int = OpTypeInt 32 1
-OpNoLine
-%int_2 = OpConstant %int 2
-OpNoLine
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-OpNoLine
-%bool = OpTypeBool
-OpNoLine
-%uint_0 = OpConstant %uint 0
-OpNoLine
-%_ptr_Function_uint = OpTypePointer Function %uint
-OpNoLine
-%int_0 = OpConstant %int 0
-OpNoLine
-%int_1 = OpConstant %int 1
-OpNoLine
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-OpNoLine
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-OpNoLine
-%uint_128 = OpConstant %uint 128
-OpNoLine
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-OpNoLine
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-OpNoLine
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-OpNoLine
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-OpNoLine
-%41 = OpTypeSampler
-OpNoLine
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-OpNoLine
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-OpNoLine
-%43 = OpTypeSampledImage %36
-OpNoLine
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-OpNoLine
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-OpNoLine
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-OpNoLine
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-OpNoLine
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-OpNoLine
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-OpNoLine
-%MainPs = OpFunction %void None %19
-OpNoLine
-%48 = OpLabel
-OpNoLine
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-OpNoLine
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-OpLine %5 23 0
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpLine %5 23 0
-OpStore %50 %49
-OpLine %5 23 0
-%51 = OpLoad %PS_INPUT %i_0
-OpLine %5 23 0
-OpStore %param %51
-OpLine %5 23 0
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-OpLine %5 23 0
-%53 = OpCompositeExtract %v4float %52 0
-OpLine %5 23 0
-OpStore %_entryPointOutput_vColor %53
-OpLine %5 23 0
-OpReturn
-OpNoLine
-OpFunctionEnd
-OpNoLine
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-OpNoLine
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-OpNoLine
-%54 = OpLabel
-OpNoLine
-%u = OpVariable %_ptr_Function_uint Function
-OpNoLine
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-OpLine %5 27 0
-%56 = OpLoad %uint %55
-OpLine %5 27 0
-%57 = OpINotEqual %bool %56 %uint_0
-OpLine %5 27 0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-OpNoLine
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-OpLine %5 28 0
-%62 = OpLoad %uint %61
-OpLine %5 28 0
-OpStore %u %62
-OpLine %5 28 0
-OpBranch %58
-OpNoLine
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-OpLine %5 30 0
-%64 = OpLoad %uint %63
-OpLine %5 30 0
-OpStore %u %64
-OpLine %5 30 0
-OpBranch %58
-OpNoLine
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-OpLine %5 31 0
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-OpLine %5 31 0
-%67 = OpLoad %36 %66
-OpLine %5 31 0
-%68 = OpLoad %41 %g_sAniso
-OpLine %5 31 0
-%69 = OpSampledImage %43 %67 %68
-OpLine %5 31 0
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-OpLine %5 31 0
-%71 = OpLoad %v2float %70
-OpLine %5 31 0
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-OpLine %5 31 0
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpLine %5 31 0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpLine %5 32 0
-OpReturnValue %74
-OpNoLine
-OpFunctionEnd
-)";
-
-  const std::string after =
-      R"(%void = OpTypeVoid
-%19 = OpTypeFunction %void
-%float = OpTypeFloat 32
-%v2float = OpTypeVector %float 2
-%PS_INPUT = OpTypeStruct %v2float
-%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
-%v4float = OpTypeVector %float 4
-%PS_OUTPUT = OpTypeStruct %v4float
-%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
-%uint = OpTypeInt 32 0
-%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
-%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
-%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
-%int = OpTypeInt 32 1
-%int_2 = OpConstant %int 2
-%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
-%bool = OpTypeBool
-%uint_0 = OpConstant %uint 0
-%_ptr_Function_uint = OpTypePointer Function %uint
-%int_0 = OpConstant %int 0
-%int_1 = OpConstant %int 1
-%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
-%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
-%uint_128 = OpConstant %uint 128
-%_arr_36_uint_128 = OpTypeArray %36 %uint_128
-%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
-%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
-%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
-%41 = OpTypeSampler
-%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
-%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
-%43 = OpTypeSampledImage %36
-%_ptr_Function_v2float = OpTypePointer Function %v2float
-%_ptr_Function_v4float = OpTypePointer Function %v4float
-%_ptr_Input_v2float = OpTypePointer Input %v2float
-%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
-%_ptr_Output_v4float = OpTypePointer Output %v4float
-%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
-%MainPs = OpFunction %void None %19
-%48 = OpLabel
-%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
-%param = OpVariable %_ptr_Function_PS_INPUT Function
-OpLine %5 23 0
-%49 = OpLoad %v2float %i_vTextureCoords
-%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
-OpStore %50 %49
-%51 = OpLoad %PS_INPUT %i_0
-OpStore %param %51
-%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
-%53 = OpCompositeExtract %v4float %52 0
-OpStore %_entryPointOutput_vColor %53
-OpReturn
-OpFunctionEnd
-%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
-%i = OpFunctionParameter %_ptr_Function_PS_INPUT
-%54 = OpLabel
-%u = OpVariable %_ptr_Function_uint Function
-%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
-OpLine %5 27 0
-%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
-%56 = OpLoad %uint %55
-%57 = OpINotEqual %bool %56 %uint_0
-OpSelectionMerge %58 None
-OpBranchConditional %57 %59 %60
-%59 = OpLabel
-OpLine %5 28 0
-%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
-%62 = OpLoad %uint %61
-OpStore %u %62
-OpBranch %58
-%60 = OpLabel
-OpLine %5 30 0
-%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
-%64 = OpLoad %uint %63
-OpStore %u %64
-OpBranch %58
-%58 = OpLabel
-OpLine %5 31 0
-%65 = OpLoad %uint %u
-%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
-%67 = OpLoad %36 %66
-%68 = OpLoad %41 %g_sAniso
-%69 = OpSampledImage %43 %67 %68
-%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
-%71 = OpLoad %v2float %70
-%72 = OpImageSampleImplicitLod %v4float %69 %71
-%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
-OpStore %73 %72
-OpLine %5 32 0
-%74 = OpLoad %PS_OUTPUT %ps_output
-OpReturnValue %74
-OpFunctionEnd
-)";
-
-  SinglePassRunAndCheck<ProcessLinesPass>(
-      predefs + before, predefs + after, false, true, kLinesEliminateDeadLines);
-}
-
-// TODO(greg-lunarg): Add tests to verify handling of these cases:
-//
-//    TODO(greg-lunarg): Think about other tests :)
-
-}  // namespace
-}  // namespace opt
-}  // namespace spvtools
diff --git a/test/opt/redundancy_elimination_test.cpp b/test/opt/redundancy_elimination_test.cpp
index 7d2abe8..474f466 100644
--- a/test/opt/redundancy_elimination_test.cpp
+++ b/test/opt/redundancy_elimination_test.cpp
@@ -272,6 +272,69 @@
   EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
 }
 
+// Test that it can get a simple case of local redundancy elimination
+// when it has OpenCL.DebugInfo.100 instructions.
+TEST_F(RedundancyEliminationTest, OpenCLDebugInfo100) {
+  // When three redundant DebugValues exist, only one DebugValue must remain.
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "OpenCL.DebugInfo.100"
+          %2 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %3 "main"
+               OpExecutionMode %3 OriginUpperLeft
+               OpSource GLSL 430
+          %4 = OpString "ps.hlsl"
+          %5 = OpString "float"
+          %6 = OpString "s0"
+          %7 = OpString "main"
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+    %uint_32 = OpConstant %uint 32
+%_ptr_Function_float = OpTypePointer Function %float
+         %15 = OpExtInst %void %1 DebugExpression
+         %16 = OpExtInst %void %1 DebugSource %4
+         %17 = OpExtInst %void %1 DebugCompilationUnit 1 4 %16 HLSL
+         %18 = OpExtInst %void %1 DebugTypeBasic %5 %uint_32 Float
+         %19 = OpExtInst %void %1 DebugTypeVector %18 4
+         %20 = OpExtInst %void %1 DebugTypeFunction FlagIsProtected|FlagIsPrivate %19
+         %21 = OpExtInst %void %1 DebugFunction %7 %20 %16 4 1 %17 %7 FlagIsProtected|FlagIsPrivate 4 %3
+; CHECK:     [[dbg_local_var:%\w+]] = OpExtInst %void {{%\w+}} DebugLocalVariable
+         %22 = OpExtInst %void %1 DebugLocalVariable %6 %19 %16 0 0 %21 FlagIsLocal
+         %14 = OpExtInst %void %1 DebugLocalVariable %6 %19 %16 0 0 %21 FlagIsLocal
+          %3 = OpFunction %void None %9
+         %23 = OpLabel
+         %24 = OpExtInst %void %1 DebugScope %21
+         %25 = OpVariable %_ptr_Function_float Function
+         %26 = OpLoad %float %25
+               OpLine %4 0 0
+; Two `OpFAdd %float %26 %26` are the same. One must be removed.
+; After removing one `OpFAdd %float %26 %26`, two DebugValues are the same.
+; One must be removed.
+;
+; CHECK:      OpLine {{%\w+}} 0 0
+; CHECK-NEXT: [[add:%\w+]] = OpFAdd %float [[value:%\w+]]
+; CHECK-NEXT: DebugValue [[dbg_local_var]] [[add]]
+; CHECK-NEXT: OpLine {{%\w+}} 1 0
+; CHECK-NEXT: OpFAdd %float [[add]] [[value]]
+; CHECK-NEXT: OpReturn
+         %27 = OpFAdd %float %26 %26
+         %28 = OpExtInst %void %1 DebugValue %22 %27 %15 %uint_0
+               OpLine %4 1 0
+         %29 = OpFAdd %float %26 %26
+         %30 = OpExtInst %void %1 DebugValue %14 %29 %15 %uint_0
+         %31 = OpExtInst %void %1 DebugValue %22 %29 %15 %uint_0
+         %32 = OpFAdd %float %29 %26
+         %33 = OpFAdd %float %27 %26
+               OpReturn
+               OpFunctionEnd
+  )";
+  SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
index 3cf46ca..2130f69 100644
--- a/test/opt/scalar_replacement_test.cpp
+++ b/test/opt/scalar_replacement_test.cpp
@@ -1899,6 +1899,186 @@
   SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
 }
 
+TEST_F(ScalarReplacementTest, DebugDeclare) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+%ext = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+%test = OpString "test"
+OpName %6 "simple_struct"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%uint_32 = OpConstant %2 32
+%3 = OpTypeStruct %2 %2 %2 %2
+%4 = OpTypePointer Function %3
+%5 = OpTypePointer Function %2
+%6 = OpTypeFunction %2
+%7 = OpConstantNull %3
+%8 = OpConstant %2 0
+%9 = OpConstant %2 1
+%10 = OpConstant %2 2
+%11 = OpConstant %2 3
+%null_expr = OpExtInst %1 %ext DebugExpression
+%src = OpExtInst %1 %ext DebugSource %test
+%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL
+%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float
+%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1
+%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12
+%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal
+%12 = OpFunction %2 None %6
+%13 = OpLabel
+%scope = OpExtInst %1 %ext DebugScope %dbg_main
+%14 = OpVariable %4 Function %7
+
+; CHECK: [[deref:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugOperation Deref
+; 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]] [[repl0]] [[deref_expr]] %int_0
+; CHECK-NOT: DebugDeclare
+%decl = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr
+
+%15 = OpInBoundsAccessChain %5 %14 %8
+%16 = OpLoad %2 %15
+%17 = OpAccessChain %5 %14 %10
+%18 = OpLoad %2 %17
+%19 = OpIAdd %2 %16 %18
+OpReturnValue %19
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DebugValue) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+%ext = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+%test = OpString "test"
+OpName %6 "simple_struct"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%uint_32 = OpConstant %2 32
+%3 = OpTypeStruct %2 %2 %2 %2
+%4 = OpTypePointer Function %3
+%5 = OpTypePointer Function %2
+%6 = OpTypeFunction %2
+%7 = OpConstantNull %3
+%8 = OpConstant %2 0
+%9 = OpConstant %2 1
+%10 = OpConstant %2 2
+%11 = OpConstant %2 3
+%deref = OpExtInst %1 %ext DebugOperation Deref
+%deref_expr = OpExtInst %1 %ext DebugExpression %deref
+%null_expr = OpExtInst %1 %ext DebugExpression
+%src = OpExtInst %1 %ext DebugSource %test
+%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL
+%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float
+%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1
+%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12
+%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal
+%12 = OpFunction %2 None %6
+%13 = OpLabel
+%scope = OpExtInst %1 %ext DebugScope %dbg_main
+%14 = OpVariable %4 Function %7
+
+; CHECK: [[deref:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugOperation Deref
+; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]]
+; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable
+; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function
+; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function
+; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function
+; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2
+; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_3
+%value = OpExtInst %1 %ext DebugValue %dbg_foo %14 %deref_expr
+
+%15 = OpInBoundsAccessChain %5 %14 %8
+%16 = OpLoad %2 %15
+%17 = OpAccessChain %5 %14 %10
+%18 = OpLoad %2 %17
+%19 = OpIAdd %2 %16 %18
+OpReturnValue %19
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DebugDeclareRecursive) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+%ext = OpExtInstImport "OpenCL.DebugInfo.100"
+OpMemoryModel Logical GLSL450
+%test = OpString "test"
+OpName %6 "simple_struct"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%uint_32 = OpConstant %2 32
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%member = OpTypeStruct %2 %float
+%3 = OpTypeStruct %2 %member %float
+%4 = OpTypePointer Function %3
+%5 = OpTypePointer Function %2
+%ptr_float_Function = OpTypePointer Function %float
+%6 = OpTypeFunction %2
+%cmember = OpConstantComposite %member %uint_32 %float_1
+%7 = OpConstantComposite %3 %uint_32 %cmember %float_1
+%8 = OpConstant %2 0
+%9 = OpConstant %2 1
+%10 = OpConstant %2 2
+%null_expr = OpExtInst %1 %ext DebugExpression
+%src = OpExtInst %1 %ext DebugSource %test
+%cu = OpExtInst %1 %ext DebugCompilationUnit 1 4 %src HLSL
+%dbg_tf = OpExtInst %1 %ext DebugTypeBasic %test %uint_32 Float
+%main_ty = OpExtInst %1 %ext DebugTypeFunction FlagIsProtected|FlagIsPrivate %1
+%dbg_main = OpExtInst %1 %ext DebugFunction %test %main_ty %src 0 0 %cu %test FlagIsProtected|FlagIsPrivate 0 %12
+%dbg_foo = OpExtInst %1 %ext DebugLocalVariable %test %dbg_tf %src 0 0 %dbg_main FlagIsLocal
+%12 = OpFunction %2 None %6
+%13 = OpLabel
+%scope = OpExtInst %1 %ext DebugScope %dbg_main
+%14 = OpVariable %4 Function %7
+
+; CHECK: [[deref:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugOperation Deref
+; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable
+; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]]
+; 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: 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
+
+%15 = OpInBoundsAccessChain %5 %14 %8
+%16 = OpLoad %2 %15
+%17 = OpAccessChain %ptr_float_Function %14 %10
+%18 = OpLoad %float %17
+%value = OpConvertFToU %2 %18
+%19 = OpIAdd %2 %16 %value
+OpReturnValue %19
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/strip_reflect_info_test.cpp b/test/opt/strip_reflect_info_test.cpp
index 5db34b7..f3fc115 100644
--- a/test/opt/strip_reflect_info_test.cpp
+++ b/test/opt/strip_reflect_info_test.cpp
@@ -25,6 +25,7 @@
 namespace {
 
 using StripLineReflectInfoTest = PassTest<::testing::Test>;
+using StripNonSemanticInfoTest = PassTest<::testing::Test>;
 
 // This test acts as an end-to-end code example on how to strip
 // reflection info from a SPIR-V module.  Use this code pattern
@@ -132,6 +133,99 @@
   SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false);
 }
 
+TEST_F(StripNonSemanticInfoTest, StripNonSemanticImport) {
+  std::string text = R"(
+; CHECK-NOT: OpExtension "SPV_KHR_non_semantic_info"
+; CHECK-NOT: OpExtInstImport
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+)";
+
+  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+}
+
+TEST_F(StripNonSemanticInfoTest, StripNonSemanticGlobal) {
+  std::string text = R"(
+; CHECK-NOT: OpExtInst
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%1 = OpExtInst %void %ext 1
+)";
+
+  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+}
+
+TEST_F(StripNonSemanticInfoTest, StripNonSemanticInFunction) {
+  std::string text = R"(
+; CHECK-NOT: OpExtInst
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+%1 = OpExtInst %void %ext 1 %foo
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+}
+
+TEST_F(StripNonSemanticInfoTest, StripNonSemanticAfterFunction) {
+  std::string text = R"(
+; CHECK-NOT: OpExtInst
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%1 = OpExtInst %void %ext 1 %foo
+)";
+
+  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+}
+
+TEST_F(StripNonSemanticInfoTest, StripNonSemanticBetweenFunctions) {
+  std::string text = R"(
+; CHECK-NOT: OpExtInst
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.Test"
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%1 = OpExtInst %void %ext 1 %foo
+%bar = OpFunction %void None %void_fn
+%bar_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<StripReflectInfoPass>(text, true);
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/struct_cfg_analysis_test.cpp b/test/opt/struct_cfg_analysis_test.cpp
index 2d1deec..e7031cb 100644
--- a/test/opt/struct_cfg_analysis_test.cpp
+++ b/test/opt/struct_cfg_analysis_test.cpp
@@ -60,7 +60,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -72,7 +74,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 0);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -84,7 +88,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -129,7 +135,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -141,7 +149,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 1);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -153,7 +163,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -165,7 +177,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 1);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(4));
@@ -215,7 +229,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -227,7 +243,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 1);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -239,7 +257,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -251,7 +271,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 1);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(4));
@@ -263,7 +285,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 1);
   EXPECT_EQ(analysis.MergeBlock(5), 6);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 1);
   EXPECT_EQ(analysis.ContainingSwitch(5), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(5));
@@ -275,7 +299,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(6), 1);
   EXPECT_EQ(analysis.ContainingLoop(6), 1);
   EXPECT_EQ(analysis.MergeBlock(6), 3);
+  EXPECT_EQ(analysis.NestingDepth(6), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(6), 1);
   EXPECT_EQ(analysis.ContainingSwitch(6), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(6));
@@ -325,7 +351,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -337,7 +365,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 0);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -349,7 +379,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -361,7 +393,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 0);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 0);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(4));
@@ -373,7 +407,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 2);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 1);
   EXPECT_EQ(analysis.ContainingSwitch(5), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(5));
@@ -385,7 +421,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(6), 2);
   EXPECT_EQ(analysis.ContainingLoop(6), 2);
   EXPECT_EQ(analysis.MergeBlock(6), 4);
+  EXPECT_EQ(analysis.NestingDepth(6), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(6), 1);
   EXPECT_EQ(analysis.ContainingSwitch(6), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(6));
@@ -433,7 +471,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -445,7 +485,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 0);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -457,7 +499,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -469,7 +513,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 0);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 0);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(4));
@@ -481,7 +527,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 0);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 0);
   EXPECT_EQ(analysis.ContainingSwitch(5), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(5));
@@ -533,7 +581,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -545,7 +595,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 1);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -557,7 +609,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -569,7 +623,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 1);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(4));
@@ -581,7 +637,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 2);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 2);
   EXPECT_EQ(analysis.ContainingSwitch(5), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(5));
@@ -593,7 +651,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(6), 2);
   EXPECT_EQ(analysis.ContainingLoop(6), 2);
   EXPECT_EQ(analysis.MergeBlock(6), 4);
+  EXPECT_EQ(analysis.NestingDepth(6), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(6), 2);
   EXPECT_EQ(analysis.ContainingSwitch(6), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(6));
@@ -605,7 +665,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(7), 1);
   EXPECT_EQ(analysis.ContainingLoop(7), 1);
   EXPECT_EQ(analysis.MergeBlock(7), 3);
+  EXPECT_EQ(analysis.NestingDepth(7), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(7), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(7), 1);
   EXPECT_EQ(analysis.ContainingSwitch(7), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(7), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(7));
@@ -645,7 +707,9 @@
     EXPECT_EQ(analysis.ContainingConstruct(i), 0);
     EXPECT_EQ(analysis.ContainingLoop(i), 0);
     EXPECT_EQ(analysis.MergeBlock(i), 0);
+    EXPECT_EQ(analysis.NestingDepth(i), 0);
     EXPECT_EQ(analysis.LoopMergeBlock(i), 0);
+    EXPECT_EQ(analysis.LoopNestingDepth(i), 0);
     EXPECT_EQ(analysis.ContainingSwitch(i), 0);
     EXPECT_EQ(analysis.SwitchMergeBlock(i), 0);
     EXPECT_FALSE(analysis.IsContinueBlock(i));
@@ -707,7 +771,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -719,7 +785,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 0);
   EXPECT_EQ(analysis.ContainingSwitch(2), 1);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 3);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -731,7 +799,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -781,7 +851,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -793,7 +865,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 0);
   EXPECT_EQ(analysis.ContainingSwitch(2), 1);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 3);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -805,7 +879,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -817,7 +893,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 0);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 0);
   EXPECT_EQ(analysis.ContainingSwitch(4), 1);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 3);
   EXPECT_FALSE(analysis.IsContinueBlock(4));
@@ -829,7 +907,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 2);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 1);
   EXPECT_EQ(analysis.ContainingSwitch(5), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(5));
@@ -841,7 +921,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(6), 2);
   EXPECT_EQ(analysis.ContainingLoop(6), 2);
   EXPECT_EQ(analysis.MergeBlock(6), 4);
+  EXPECT_EQ(analysis.NestingDepth(6), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(6), 1);
   EXPECT_EQ(analysis.ContainingSwitch(6), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(6));
@@ -889,7 +971,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -901,7 +985,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 0);
   EXPECT_EQ(analysis.ContainingSwitch(2), 1);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 3);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -913,7 +999,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -925,7 +1013,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 0);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 0);
   EXPECT_EQ(analysis.ContainingSwitch(4), 1);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 3);
   EXPECT_FALSE(analysis.IsContinueBlock(4));
@@ -937,7 +1027,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 0);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 0);
   EXPECT_EQ(analysis.ContainingSwitch(5), 1);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 3);
   EXPECT_FALSE(analysis.IsContinueBlock(5));
@@ -985,7 +1077,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -997,7 +1091,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 0);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 0);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -1009,7 +1105,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -1021,7 +1119,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 0);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 0);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(4));
@@ -1033,7 +1133,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 2);
   EXPECT_EQ(analysis.ContainingLoop(5), 0);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 0);
   EXPECT_EQ(analysis.ContainingSwitch(5), 2);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 4);
   EXPECT_FALSE(analysis.IsContinueBlock(5));
@@ -1083,7 +1185,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -1095,7 +1199,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 1);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -1107,7 +1213,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -1119,7 +1227,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 1);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(4));
@@ -1131,7 +1241,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 4);
   EXPECT_EQ(analysis.ContainingLoop(5), 1);
   EXPECT_EQ(analysis.MergeBlock(5), 6);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 1);
   EXPECT_EQ(analysis.ContainingSwitch(5), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(5));
@@ -1143,7 +1255,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(6), 1);
   EXPECT_EQ(analysis.ContainingLoop(6), 1);
   EXPECT_EQ(analysis.MergeBlock(6), 3);
+  EXPECT_EQ(analysis.NestingDepth(6), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(6), 1);
   EXPECT_EQ(analysis.ContainingSwitch(6), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(6));
@@ -1195,7 +1309,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(1), 0);
   EXPECT_EQ(analysis.ContainingLoop(1), 0);
   EXPECT_EQ(analysis.MergeBlock(1), 0);
+  EXPECT_EQ(analysis.NestingDepth(1), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(1), 0);
   EXPECT_EQ(analysis.ContainingSwitch(1), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(1), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(1));
@@ -1207,7 +1323,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(2), 1);
   EXPECT_EQ(analysis.ContainingLoop(2), 1);
   EXPECT_EQ(analysis.MergeBlock(2), 3);
+  EXPECT_EQ(analysis.NestingDepth(2), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(2), 1);
   EXPECT_EQ(analysis.ContainingSwitch(2), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(2), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(2));
@@ -1219,7 +1337,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(3), 0);
   EXPECT_EQ(analysis.ContainingLoop(3), 0);
   EXPECT_EQ(analysis.MergeBlock(3), 0);
+  EXPECT_EQ(analysis.NestingDepth(3), 0);
   EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+  EXPECT_EQ(analysis.LoopNestingDepth(3), 0);
   EXPECT_EQ(analysis.ContainingSwitch(3), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(3), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(3));
@@ -1231,7 +1351,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(4), 1);
   EXPECT_EQ(analysis.ContainingLoop(4), 1);
   EXPECT_EQ(analysis.MergeBlock(4), 3);
+  EXPECT_EQ(analysis.NestingDepth(4), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(4), 1);
   EXPECT_EQ(analysis.ContainingSwitch(4), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(4), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(4));
@@ -1243,7 +1365,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(5), 7);
   EXPECT_EQ(analysis.ContainingLoop(5), 7);
   EXPECT_EQ(analysis.MergeBlock(5), 4);
+  EXPECT_EQ(analysis.NestingDepth(5), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(5), 2);
   EXPECT_EQ(analysis.ContainingSwitch(5), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(5), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(5));
@@ -1255,7 +1379,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(6), 7);
   EXPECT_EQ(analysis.ContainingLoop(6), 7);
   EXPECT_EQ(analysis.MergeBlock(6), 4);
+  EXPECT_EQ(analysis.NestingDepth(6), 2);
   EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+  EXPECT_EQ(analysis.LoopNestingDepth(6), 2);
   EXPECT_EQ(analysis.ContainingSwitch(6), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(6), 0);
   EXPECT_FALSE(analysis.IsContinueBlock(6));
@@ -1267,7 +1393,9 @@
   EXPECT_EQ(analysis.ContainingConstruct(7), 1);
   EXPECT_EQ(analysis.ContainingLoop(7), 1);
   EXPECT_EQ(analysis.MergeBlock(7), 3);
+  EXPECT_EQ(analysis.NestingDepth(7), 1);
   EXPECT_EQ(analysis.LoopMergeBlock(7), 3);
+  EXPECT_EQ(analysis.LoopNestingDepth(7), 1);
   EXPECT_EQ(analysis.ContainingSwitch(7), 0);
   EXPECT_EQ(analysis.SwitchMergeBlock(7), 0);
   EXPECT_TRUE(analysis.IsContinueBlock(7));
diff --git a/test/opt/value_table_test.cpp b/test/opt/value_table_test.cpp
index 76e7f73..c760f98 100644
--- a/test/opt/value_table_test.cpp
+++ b/test/opt/value_table_test.cpp
@@ -728,6 +728,105 @@
   EXPECT_EQ(vtable.GetValueNumber(load1), vtable.GetValueNumber(load2));
 }
 
+TEST_F(ValueTableTest, DifferentDebugLocalVariableSameValue) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+          %2 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %3 "main"
+               OpExecutionMode %3 OriginUpperLeft
+               OpSource GLSL 430
+          %4 = OpString "test"
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 32
+          %9 = OpExtInst %5 %2 DebugSource %4
+         %10 = OpExtInst %5 %2 DebugCompilationUnit 1 4 %9 HLSL
+         %11 = OpExtInst %5 %2 DebugTypeBasic %4 %8 Float
+         %12 = OpExtInst %5 %2 DebugLocalVariable %4 %11 %9 0 0 %10 FlagIsLocal
+         %13 = OpExtInst %5 %2 DebugLocalVariable %4 %11 %9 0 0 %10 FlagIsLocal
+          %3 = OpFunction %5 None %6
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  ValueNumberTable vtable(context.get());
+  Instruction* inst1 = context->get_def_use_mgr()->GetDef(12);
+  Instruction* inst2 = context->get_def_use_mgr()->GetDef(13);
+  EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentDebugValueSameValue) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+          %2 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %3 "main"
+               OpExecutionMode %3 OriginUpperLeft
+               OpSource GLSL 430
+          %4 = OpString "test"
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 0
+          %8 = OpConstant %7 32
+          %9 = OpExtInst %5 %2 DebugSource %4
+         %10 = OpExtInst %5 %2 DebugCompilationUnit 1 4 %9 HLSL
+         %11 = OpExtInst %5 %2 DebugTypeBasic %4 %8 Float
+         %12 = OpExtInst %5 %2 DebugLocalVariable %4 %11 %9 0 0 %10 FlagIsLocal
+         %13 = OpExtInst %5 %2 DebugExpression
+          %3 = OpFunction %5 None %6
+         %14 = OpLabel
+         %15 = OpExtInst %5 %2 DebugValue %12 %8 %13
+         %16 = OpExtInst %5 %2 DebugValue %12 %8 %13
+               OpReturn
+               OpFunctionEnd
+  )";
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  ValueNumberTable vtable(context.get());
+  Instruction* inst1 = context->get_def_use_mgr()->GetDef(15);
+  Instruction* inst2 = context->get_def_use_mgr()->GetDef(16);
+  EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentDebugDeclareSameValue) {
+  const std::string text = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+          %2 = OpExtInstImport "OpenCL.DebugInfo.100"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %3 "main"
+               OpExecutionMode %3 OriginUpperLeft
+               OpSource GLSL 430
+          %4 = OpString "test"
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+%_ptr_Function_uint = OpTypePointer Function %uint
+    %uint_32 = OpConstant %uint 32
+         %10 = OpExtInst %void %2 DebugSource %4
+         %11 = OpExtInst %void %2 DebugCompilationUnit 1 4 %10 HLSL
+         %12 = OpExtInst %void %2 DebugTypeBasic %4 %uint_32 Float
+         %13 = OpExtInst %void %2 DebugLocalVariable %4 %12 %10 0 0 %11 FlagIsLocal
+         %14 = OpExtInst %void %2 DebugExpression
+          %3 = OpFunction %void None %6
+         %15 = OpLabel
+         %16 = OpVariable %_ptr_Function_uint Function
+         %17 = OpExtInst %void %2 DebugDeclare %13 %16 %14
+         %18 = OpExtInst %void %2 DebugDeclare %13 %16 %14
+               OpReturn
+               OpFunctionEnd
+  )";
+  auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  ValueNumberTable vtable(context.get());
+  Instruction* inst1 = context->get_def_use_mgr()->GetDef(17);
+  Instruction* inst2 = context->get_def_use_mgr()->GetDef(18);
+  EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
 }  // namespace
 }  // namespace opt
 }  // namespace spvtools
diff --git a/test/opt/wrap_opkill_test.cpp b/test/opt/wrap_opkill_test.cpp
index e944109..e40d701 100644
--- a/test/opt/wrap_opkill_test.cpp
+++ b/test/opt/wrap_opkill_test.cpp
@@ -903,7 +903,7 @@
 ; CHECK-NEXT: {{%\d+}} = OpExtInst %void [[ext:%\d+]] DebugScope
 ; CHECK-NEXT: OpLine [[file:%\d+]] 100 200
 ; CHECK-NEXT: OpFunctionCall %void [[new_kill:%\w+]]
-; CHECK-NEXT: {{%\d+}} = OpExtInst %void [[ext]] DebugNoScope
+; CHECK:      {{%\d+}} = OpExtInst %void [[ext]] DebugNoScope
 ; CHECK-NEXT: OpReturn
 ; CHECK: [[new_kill]] = OpFunction
 ; CHECK-NEXT: OpLabel
diff --git a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp
index 69ef1f4..0e46114 100644
--- a/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp
+++ b/test/reduce/conditional_branch_to_simple_conditional_branch_test.cpp
@@ -80,7 +80,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(2, ops.size());
 
@@ -125,7 +125,7 @@
   }
 
   ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 
   // Start again, and apply the other op.
@@ -134,7 +134,7 @@
   CheckValid(kEnv, context.get());
 
   ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(2, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -225,7 +225,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(0, ops.size());
 }
@@ -276,7 +276,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -313,7 +313,7 @@
   CheckEqual(kEnv, after, context.get());
 
   ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -361,7 +361,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -396,7 +396,7 @@
   CheckEqual(kEnv, after, context.get());
 
   ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -453,7 +453,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -492,7 +492,7 @@
   CheckEqual(kEnv, after, context.get());
 
   ops = ConditionalBranchToSimpleConditionalBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/merge_blocks_test.cpp b/test/reduce/merge_blocks_test.cpp
index dfb614e..8506ee0 100644
--- a/test/reduce/merge_blocks_test.cpp
+++ b/test/reduce/merge_blocks_test.cpp
@@ -66,7 +66,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(5, ops.size());
 
   // Try order 3, 0, 2, 4, 1
@@ -356,7 +356,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(11, ops.size());
 
   for (auto& ri : ops) {
@@ -474,7 +474,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -556,7 +556,7 @@
   ASSERT_NE(context.get(), nullptr);
   auto opportunities =
       MergeBlocksReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
 
   // A->B and B->C
   ASSERT_EQ(opportunities.size(), 2);
diff --git a/test/reduce/operand_to_constant_test.cpp b/test/reduce/operand_to_constant_test.cpp
index b2f67ee..f44de51 100644
--- a/test/reduce/operand_to_constant_test.cpp
+++ b/test/reduce/operand_to_constant_test.cpp
@@ -101,7 +101,7 @@
       BuildModule(env, consumer, original, kReduceAssembleOption);
   const auto ops =
       OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(17, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
@@ -151,10 +151,158 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
+TEST(OperandToConstantReductionPassTest, TargetSpecificFunction) {
+  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 = OpTypeFunction %6 %7
+         %17 = OpConstant %6 1
+         %20 = OpConstant %6 2
+         %23 = OpConstant %6 0
+         %24 = OpTypeBool
+         %35 = OpConstant %6 3
+         %53 = OpConstant %6 10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %65 = OpVariable %7 Function
+         %68 = OpVariable %7 Function
+         %73 = OpVariable %7 Function
+               OpStore %65 %35
+         %66 = OpLoad %6 %65
+         %67 = OpIAdd %6 %66 %17
+               OpStore %65 %67
+         %69 = OpLoad %6 %65
+               OpStore %68 %69
+         %70 = OpFunctionCall %6 %13 %68
+         %71 = OpLoad %6 %65
+         %72 = OpIAdd %6 %71 %70
+               OpStore %65 %72
+         %74 = OpLoad %6 %65
+               OpStore %73 %74
+         %75 = OpFunctionCall %6 %10 %73
+         %76 = OpLoad %6 %65
+         %77 = OpIAdd %6 %76 %75
+               OpStore %65 %77
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %15 = OpVariable %7 Function
+         %16 = OpLoad %6 %9
+         %18 = OpIAdd %6 %16 %17
+               OpStore %15 %18
+         %19 = OpLoad %6 %15
+         %21 = OpIAdd %6 %19 %20
+               OpStore %15 %21
+         %22 = OpLoad %6 %15
+         %25 = OpSGreaterThan %24 %22 %23
+               OpSelectionMerge %27 None
+               OpBranchConditional %25 %26 %27
+         %26 = OpLabel
+         %28 = OpLoad %6 %9
+               OpReturnValue %28
+         %27 = OpLabel
+         %30 = OpLoad %6 %9
+         %31 = OpIAdd %6 %30 %17
+               OpReturnValue %31
+               OpFunctionEnd
+         %13 = OpFunction %6 None %8
+         %12 = OpFunctionParameter %7
+         %14 = OpLabel
+         %41 = OpVariable %7 Function
+         %46 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %34 = OpLoad %6 %12
+         %36 = OpIEqual %24 %34 %35
+               OpSelectionMerge %38 None
+               OpBranchConditional %36 %37 %38
+         %37 = OpLabel
+         %39 = OpLoad %6 %12
+         %40 = OpIMul %6 %20 %39
+               OpStore %41 %40
+         %42 = OpFunctionCall %6 %10 %41
+               OpReturnValue %42
+         %38 = OpLabel
+         %44 = OpLoad %6 %12
+         %45 = OpIAdd %6 %44 %17
+               OpStore %12 %45
+               OpStore %46 %23
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %6 %46
+         %54 = OpSLessThan %24 %52 %53
+               OpBranchConditional %54 %48 %49
+         %48 = OpLabel
+         %56 = OpLoad %6 %12
+               OpStore %55 %56
+         %57 = OpFunctionCall %6 %10 %55
+         %58 = OpLoad %6 %12
+         %59 = OpIAdd %6 %58 %57
+               OpStore %12 %59
+               OpBranch %50
+         %50 = OpLabel
+         %60 = OpLoad %6 %46
+         %61 = OpIAdd %6 %60 %17
+               OpStore %46 %61
+               OpBranch %47
+         %49 = OpLabel
+         %62 = OpLoad %6 %12
+               OpReturnValue %62
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, shader, kReduceAssembleOption);
+
+  // Targeting all functions, there are quite a few opportunities.  To avoid
+  // making the test too sensitive, we check that there are more than a number
+  // somewhat lower than the real number.
+  const auto all_ops =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get(), 0);
+  ASSERT_TRUE(all_ops.size() > 100);
+
+  // Targeting individual functions, there are fewer opportunities.  Again, we
+  // avoid checking against an exact number so that the test is not too
+  // sensitive.
+  const auto ops_for_function_4 =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get(), 4);
+  const auto ops_for_function_10 =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get(), 10);
+  const auto ops_for_function_13 =
+      OperandToConstReductionOpportunityFinder().GetAvailableOpportunities(
+          context.get(), 13);
+  ASSERT_TRUE(ops_for_function_4.size() < 60);
+  ASSERT_TRUE(ops_for_function_10.size() < 50);
+  ASSERT_TRUE(ops_for_function_13.size() < 80);
+
+  // The total number of opportunities should be the sum of the per-function
+  // opportunities.
+  ASSERT_EQ(all_ops.size(), ops_for_function_4.size() +
+                                ops_for_function_10.size() +
+                                ops_for_function_13.size());
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/operand_to_dominating_id_test.cpp b/test/reduce/operand_to_dominating_id_test.cpp
index cd5b2c6..697c5cb 100644
--- a/test/reduce/operand_to_dominating_id_test.cpp
+++ b/test/reduce/operand_to_dominating_id_test.cpp
@@ -56,7 +56,7 @@
   const auto context =
       BuildModule(env, consumer, original, kReduceAssembleOption);
   const auto ops = OperandToDominatingIdReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(10, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
diff --git a/test/reduce/operand_to_undef_test.cpp b/test/reduce/operand_to_undef_test.cpp
index fa64bd5..4197427 100644
--- a/test/reduce/operand_to_undef_test.cpp
+++ b/test/reduce/operand_to_undef_test.cpp
@@ -167,7 +167,7 @@
       BuildModule(env, consumer, original, kReduceAssembleOption);
   const auto ops =
       OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
 
   ASSERT_EQ(10, ops.size());
 
@@ -221,7 +221,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       OperandToUndefReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/reducer_test.cpp b/test/reduce/reducer_test.cpp
index 0de5af1..276aedc 100644
--- a/test/reduce/reducer_test.cpp
+++ b/test/reduce/reducer_test.cpp
@@ -14,6 +14,8 @@
 
 #include "source/reduce/reducer.h"
 
+#include <unordered_map>
+
 #include "source/opt/build_module.h"
 #include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
 #include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
@@ -23,11 +25,8 @@
 namespace reduce {
 namespace {
 
-using opt::BasicBlock;
-using opt::IRContext;
-
 const spv_target_env kEnv = SPV_ENV_UNIVERSAL_1_3;
-const MessageConsumer kMessageConsumer = CLIMessageConsumer;
+const MessageConsumer kMessageConsumer = NopDiagnostic;
 
 // This changes its mind each time IsInteresting is invoked as to whether the
 // binary is interesting, until some limit is reached after which the binary is
@@ -42,7 +41,7 @@
         always_interesting_after_(always_interesting_after),
         count_(0) {}
 
-  bool IsInteresting(const std::vector<uint32_t>&) {
+  bool IsInteresting() {
     bool result;
     if (count_ > always_interesting_after_) {
       result = true;
@@ -194,10 +193,10 @@
 
   Reducer reducer(kEnv);
   PingPongInteresting ping_pong_interesting(10);
-  reducer.SetMessageConsumer(NopDiagnostic);
+  reducer.SetMessageConsumer(kMessageConsumer);
   reducer.SetInterestingnessFunction(
-      [&](const std::vector<uint32_t>& binary, uint32_t) -> bool {
-        return ping_pong_interesting.IsInteresting(binary);
+      [&ping_pong_interesting](const std::vector<uint32_t>&, uint32_t) -> bool {
+        return ping_pong_interesting.IsInteresting();
       });
   reducer.AddReductionPass(
       MakeUnique<RemoveUnusedInstructionReductionOpportunityFinder>(false));
@@ -230,13 +229,14 @@
     DumpShader(binary, ss.str().c_str());
   }
 
-  std::unique_ptr<IRContext> context =
+  std::unique_ptr<opt::IRContext> context =
       BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size());
   assert(context);
   bool interesting = false;
   for (auto& function : *context->module()) {
     context->cfg()->ForEachBlockInPostOrder(
-        &*function.begin(), [opcode, &interesting](BasicBlock* block) -> void {
+        &*function.begin(),
+        [opcode, &interesting](opt::BasicBlock* block) -> void {
           for (auto& inst : *block) {
             if (inst.opcode() == opcode) {
               interesting = true;
@@ -369,6 +369,147 @@
                OpFunctionEnd
   )";
 
+// The shader below comes from the following GLSL.
+// #version 320 es
+//
+//  int baz(int x) {
+//   int y = x + 1;
+//   y = y + 2;
+//   if (y > 0) {
+//     return x;
+//   }
+//   return x + 1;
+// }
+//
+//  int bar(int a) {
+//   if (a == 3) {
+//     return baz(2*a);
+//   }
+//   a = a + 1;
+//   for (int i = 0; i < 10; i++) {
+//     a += baz(a);
+//   }
+//   return a;
+// }
+//
+//  void main() {
+//   int x;
+//   x = 3;
+//   x += 1;
+//   x += bar(x);
+//   x += baz(x);
+// }
+const std::string kShaderWithMultipleFunctions = 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 = OpTypeFunction %6 %7
+         %17 = OpConstant %6 1
+         %20 = OpConstant %6 2
+         %23 = OpConstant %6 0
+         %24 = OpTypeBool
+         %35 = OpConstant %6 3
+         %53 = OpConstant %6 10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %65 = OpVariable %7 Function
+         %68 = OpVariable %7 Function
+         %73 = OpVariable %7 Function
+               OpStore %65 %35
+         %66 = OpLoad %6 %65
+         %67 = OpIAdd %6 %66 %17
+               OpStore %65 %67
+         %69 = OpLoad %6 %65
+               OpStore %68 %69
+         %70 = OpFunctionCall %6 %13 %68
+         %71 = OpLoad %6 %65
+         %72 = OpIAdd %6 %71 %70
+               OpStore %65 %72
+         %74 = OpLoad %6 %65
+               OpStore %73 %74
+         %75 = OpFunctionCall %6 %10 %73
+         %76 = OpLoad %6 %65
+         %77 = OpIAdd %6 %76 %75
+               OpStore %65 %77
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %15 = OpVariable %7 Function
+         %16 = OpLoad %6 %9
+         %18 = OpIAdd %6 %16 %17
+               OpStore %15 %18
+         %19 = OpLoad %6 %15
+         %21 = OpIAdd %6 %19 %20
+               OpStore %15 %21
+         %22 = OpLoad %6 %15
+         %25 = OpSGreaterThan %24 %22 %23
+               OpSelectionMerge %27 None
+               OpBranchConditional %25 %26 %27
+         %26 = OpLabel
+         %28 = OpLoad %6 %9
+               OpReturnValue %28
+         %27 = OpLabel
+         %30 = OpLoad %6 %9
+         %31 = OpIAdd %6 %30 %17
+               OpReturnValue %31
+               OpFunctionEnd
+         %13 = OpFunction %6 None %8
+         %12 = OpFunctionParameter %7
+         %14 = OpLabel
+         %41 = OpVariable %7 Function
+         %46 = OpVariable %7 Function
+         %55 = OpVariable %7 Function
+         %34 = OpLoad %6 %12
+         %36 = OpIEqual %24 %34 %35
+               OpSelectionMerge %38 None
+               OpBranchConditional %36 %37 %38
+         %37 = OpLabel
+         %39 = OpLoad %6 %12
+         %40 = OpIMul %6 %20 %39
+               OpStore %41 %40
+         %42 = OpFunctionCall %6 %10 %41
+               OpReturnValue %42
+         %38 = OpLabel
+         %44 = OpLoad %6 %12
+         %45 = OpIAdd %6 %44 %17
+               OpStore %12 %45
+               OpStore %46 %23
+               OpBranch %47
+         %47 = OpLabel
+               OpLoopMerge %49 %50 None
+               OpBranch %51
+         %51 = OpLabel
+         %52 = OpLoad %6 %46
+         %54 = OpSLessThan %24 %52 %53
+               OpBranchConditional %54 %48 %49
+         %48 = OpLabel
+         %56 = OpLoad %6 %12
+               OpStore %55 %56
+         %57 = OpFunctionCall %6 %10 %55
+         %58 = OpLoad %6 %12
+         %59 = OpIAdd %6 %58 %57
+               OpStore %12 %59
+               OpBranch %50
+         %50 = OpLabel
+         %60 = OpLoad %6 %46
+         %61 = OpIAdd %6 %60 %17
+               OpStore %46 %61
+               OpBranch %47
+         %49 = OpLabel
+         %62 = OpLoad %6 %12
+               OpReturnValue %62
+               OpFunctionEnd
+  )";
+
 TEST(ReducerTest, ShaderReduceWhileMulReachable) {
   Reducer reducer(kEnv);
 
@@ -417,6 +558,70 @@
   ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
 }
 
+// Computes an instruction count for each function in the module represented by
+// |binary|.
+std::unordered_map<uint32_t, uint32_t> GetFunctionInstructionCount(
+    const std::vector<uint32_t>& binary) {
+  std::unique_ptr<opt::IRContext> context =
+      BuildModule(kEnv, kMessageConsumer, binary.data(), binary.size());
+  assert(context != nullptr && "Failed to build module.");
+  std::unordered_map<uint32_t, uint32_t> result;
+  for (auto& function : *context->module()) {
+    uint32_t& count = result[function.result_id()] = 0;
+    function.ForEachInst([&count](opt::Instruction*) { count++; });
+  }
+  return result;
+}
+
+TEST(ReducerTest, SingleFunctionReduction) {
+  Reducer reducer(kEnv);
+
+  PingPongInteresting ping_pong_interesting(4);
+  reducer.SetInterestingnessFunction(
+      [&ping_pong_interesting](const std::vector<uint32_t>&, uint32_t) -> bool {
+        return ping_pong_interesting.IsInteresting();
+      });
+  reducer.AddDefaultReductionPasses();
+  reducer.SetMessageConsumer(kMessageConsumer);
+
+  std::vector<uint32_t> binary_in;
+  SpirvTools t(kEnv);
+
+  ASSERT_TRUE(t.Assemble(kShaderWithMultipleFunctions, &binary_in,
+                         kReduceAssembleOption));
+
+  auto original_instruction_count = GetFunctionInstructionCount(binary_in);
+
+  std::vector<uint32_t> binary_out;
+  spvtools::ReducerOptions reducer_options;
+  reducer_options.set_step_limit(500);
+  reducer_options.set_fail_on_validation_error(true);
+
+  // Instruct the reducer to only target function 13.
+  reducer_options.set_target_function(13);
+
+  spvtools::ValidatorOptions validator_options;
+
+  Reducer::ReductionResultStatus status = reducer.Run(
+      std::move(binary_in), &binary_out, reducer_options, validator_options);
+
+  ASSERT_EQ(status, Reducer::ReductionResultStatus::kComplete);
+
+  auto final_instruction_count = GetFunctionInstructionCount(binary_out);
+
+  // Nothing should have been removed from these functions.
+  ASSERT_EQ(original_instruction_count.at(4), final_instruction_count.at(4));
+  ASSERT_EQ(original_instruction_count.at(10), final_instruction_count.at(10));
+
+  // Function 13 should have been reduced to these five instructions:
+  //   OpFunction
+  //   OpFunctionParameter
+  //   OpLabel
+  //   OpReturnValue
+  //   OpFunctionEnd
+  ASSERT_EQ(5, final_instruction_count.at(13));
+}
+
 }  // namespace
 }  // namespace reduce
 }  // namespace spvtools
diff --git a/test/reduce/remove_block_test.cpp b/test/reduce/remove_block_test.cpp
index f31cc9d..2500d0c 100644
--- a/test/reduce/remove_block_test.cpp
+++ b/test/reduce/remove_block_test.cpp
@@ -66,7 +66,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(2, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -181,7 +181,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -209,7 +209,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -247,7 +247,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   const auto ops =
       RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -287,7 +287,7 @@
   const auto context =
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   auto ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
-      context.get());
+      context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -323,7 +323,7 @@
   // removed.
 
   ops = RemoveBlockReductionOpportunityFinder().GetAvailableOpportunities(
-      context.get());
+      context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
diff --git a/test/reduce/remove_function_test.cpp b/test/reduce/remove_function_test.cpp
index 576b603..e293f4e 100644
--- a/test/reduce/remove_function_test.cpp
+++ b/test/reduce/remove_function_test.cpp
@@ -67,7 +67,7 @@
 
   auto ops =
       RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -97,7 +97,7 @@
   CheckEqual(env, after_first, context.get());
 
   ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
-      context.get());
+      context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -156,7 +156,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   auto ops =
       RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -193,7 +193,7 @@
 
   auto ops =
       RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(2, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -254,7 +254,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   auto ops =
       RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -286,7 +286,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
   auto ops =
       RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/remove_selection_test.cpp b/test/reduce/remove_selection_test.cpp
index f8acd5d..2921bbe 100644
--- a/test/reduce/remove_selection_test.cpp
+++ b/test/reduce/remove_selection_test.cpp
@@ -62,7 +62,7 @@
 
   auto ops =
       RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -96,7 +96,7 @@
   CheckEqual(env, after, context.get());
 
   ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-      context.get());
+      context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -136,7 +136,7 @@
 
   auto ops =
       RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -168,7 +168,7 @@
   CheckEqual(env, after, context.get());
 
   ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-      context.get());
+      context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -212,7 +212,7 @@
 
   auto ops =
       RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -258,7 +258,7 @@
 
   auto ops =
       RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -306,7 +306,7 @@
 
   auto ops =
       RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -384,7 +384,7 @@
 
   auto ops =
       RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -427,7 +427,7 @@
   CheckEqual(env, after, context.get());
 
   ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-      context.get());
+      context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -505,7 +505,7 @@
 
   auto ops =
       RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-          context.get());
+          context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -548,7 +548,7 @@
   CheckEqual(env, after, context.get());
 
   ops = RemoveSelectionReductionOpportunityFinder().GetAvailableOpportunities(
-      context.get());
+      context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/remove_unused_instruction_test.cpp b/test/reduce/remove_unused_instruction_test.cpp
index 68bc601..eb548e1 100644
--- a/test/reduce/remove_unused_instruction_test.cpp
+++ b/test/reduce/remove_unused_instruction_test.cpp
@@ -72,7 +72,7 @@
 
   CheckValid(kEnv, context.get());
 
-  auto ops = finder.GetAvailableOpportunities(context.get());
+  auto ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(10, ops.size());
 
@@ -108,7 +108,7 @@
 
   CheckEqual(kEnv, step_2, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(7, ops.size());
 
@@ -137,7 +137,7 @@
 
   CheckEqual(kEnv, step_3, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -165,7 +165,7 @@
 
   CheckEqual(kEnv, step_4, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -192,7 +192,7 @@
 
   CheckEqual(kEnv, step_5, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -218,7 +218,7 @@
 
   CheckEqual(kEnv, step_6, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(0, ops.size());
 }
@@ -258,7 +258,7 @@
 
   CheckValid(kEnv, context.get());
 
-  auto ops = finder.GetAvailableOpportunities(context.get());
+  auto ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(6, ops.size());
 
@@ -289,7 +289,7 @@
 
   CheckEqual(kEnv, after, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(3, ops.size());
 
@@ -317,7 +317,7 @@
 
   CheckEqual(kEnv, after_2, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -344,7 +344,7 @@
 
   CheckEqual(kEnv, after_3, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -370,7 +370,7 @@
 
   CheckEqual(kEnv, after_4, context.get());
 
-  ops = finder.GetAvailableOpportunities(context.get());
+  ops = finder.GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(0, ops.size());
 }
@@ -438,7 +438,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
 
   auto ops = RemoveUnusedInstructionReductionOpportunityFinder(true)
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(7, ops.size());
 
   for (auto& op : ops) {
@@ -487,7 +487,7 @@
   CheckEqual(env, expected_1, context.get());
 
   ops = RemoveUnusedInstructionReductionOpportunityFinder(true)
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(6, ops.size());
 
   for (auto& op : ops) {
@@ -530,7 +530,7 @@
   CheckEqual(env, expected_2, context.get());
 
   ops = RemoveUnusedInstructionReductionOpportunityFinder(true)
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(6, ops.size());
 
   for (auto& op : ops) {
diff --git a/test/reduce/remove_unused_struct_member_test.cpp b/test/reduce/remove_unused_struct_member_test.cpp
index 402ef2d..d3c1487 100644
--- a/test/reduce/remove_unused_struct_member_test.cpp
+++ b/test/reduce/remove_unused_struct_member_test.cpp
@@ -61,7 +61,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
 
   auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
@@ -143,7 +143,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
 
   auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
   ops[0]->TryToApply();
@@ -229,7 +229,7 @@
       BuildModule(env, consumer, shader, kReduceAssembleOption);
 
   auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/simple_conditional_branch_to_branch_test.cpp b/test/reduce/simple_conditional_branch_to_branch_test.cpp
index d55e691..fcc9d72 100644
--- a/test/reduce/simple_conditional_branch_to_branch_test.cpp
+++ b/test/reduce/simple_conditional_branch_to_branch_test.cpp
@@ -73,7 +73,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(0, ops.size());
 }
@@ -122,7 +122,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -161,7 +161,7 @@
   CheckEqual(kEnv, after, context.get());
 
   ops = SimpleConditionalBranchToBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -217,7 +217,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(0, ops.size());
 }
@@ -266,7 +266,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -303,7 +303,7 @@
   CheckEqual(kEnv, after, context.get());
 
   ops = SimpleConditionalBranchToBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -349,7 +349,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -384,7 +384,7 @@
   CheckEqual(kEnv, after, context.get());
 
   ops = SimpleConditionalBranchToBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -438,7 +438,7 @@
   CheckValid(kEnv, context.get());
 
   auto ops = SimpleConditionalBranchToBranchOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
 
@@ -477,7 +477,7 @@
   CheckEqual(kEnv, after, context.get());
 
   ops = SimpleConditionalBranchToBranchOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/structured_loop_to_selection_test.cpp b/test/reduce/structured_loop_to_selection_test.cpp
index 95b5f4f..0cfcfdf 100644
--- a/test/reduce/structured_loop_to_selection_test.cpp
+++ b/test/reduce/structured_loop_to_selection_test.cpp
@@ -65,7 +65,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -211,7 +211,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(4, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -680,7 +680,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
@@ -758,7 +758,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   // Initially there are two opportunities.
   ASSERT_EQ(2, ops.size());
@@ -881,7 +881,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -956,7 +956,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1024,7 +1024,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1224,7 +1224,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -1691,7 +1691,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2008,7 +2008,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2156,7 +2156,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2313,7 +2313,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2421,7 +2421,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2511,7 +2511,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   // There should be no opportunities.
   ASSERT_EQ(0, ops.size());
@@ -2555,7 +2555,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   // There should be no opportunities.
   ASSERT_EQ(0, ops.size());
@@ -2595,7 +2595,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2670,7 +2670,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2733,7 +2733,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2790,7 +2790,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2874,7 +2874,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -2970,7 +2970,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3089,7 +3089,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(2, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3209,7 +3209,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   // We cannot transform the inner loop due to its header jumping straight to
   // the outer loop merge (the inner loop's merge does not post-dominate its
@@ -3254,7 +3254,7 @@
 
   // Now look again for more opportunities.
   ops = StructuredLoopToSelectionReductionOpportunityFinder()
-            .GetAvailableOpportunities(context.get());
+            .GetAvailableOpportunities(context.get(), 0);
 
   // What was the inner loop should now be transformable, as the jump to the
   // outer loop's merge has been redirected.
@@ -3422,7 +3422,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                 .GetAvailableOpportunities(context.get());
+                 .GetAvailableOpportunities(context.get(), 0);
 
   ASSERT_EQ(1, ops.size());
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3513,7 +3513,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(1, ops.size());
 
   ASSERT_TRUE(ops[0]->PreconditionHolds());
@@ -3619,7 +3619,7 @@
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption);
   const auto ops = StructuredLoopToSelectionReductionOpportunityFinder()
-                       .GetAvailableOpportunities(context.get());
+                       .GetAvailableOpportunities(context.get(), 0);
   ASSERT_EQ(0, ops.size());
 }
 
diff --git a/test/reduce/validation_during_reduction_test.cpp b/test/reduce/validation_during_reduction_test.cpp
index 2981c2e..d864344 100644
--- a/test/reduce/validation_during_reduction_test.cpp
+++ b/test/reduce/validation_during_reduction_test.cpp
@@ -22,10 +22,10 @@
 namespace {
 
 using opt::Function;
-using opt::Instruction;
 using opt::IRContext;
+using opt::Instruction;
 
-// A dumb reduction opportunity finder that finds opportunities to remove global
+// A reduction opportunity finder that finds opportunities to remove global
 // values regardless of whether they are referenced. This is very likely to make
 // the resulting module invalid.  We use this to test the reducer's behavior in
 // the scenario where a bad reduction pass leads to an invalid module.
@@ -43,7 +43,7 @@
   // referenced (directly or indirectly) from elsewhere in the module, each such
   // opportunity will make the module invalid.
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      IRContext* context) const final {
+      IRContext* context, uint32_t /*unused*/) const final {
     std::vector<std::unique_ptr<ReductionOpportunity>> result;
     for (auto& inst : context->module()->types_values()) {
       if (inst.HasResultId()) {
@@ -55,7 +55,7 @@
   }
 };
 
-// A dumb reduction opportunity that exists at the start of every function whose
+// A reduction opportunity that exists at the start of every function whose
 // first instruction is an OpVariable instruction. When applied, the OpVariable
 // instruction is duplicated (with a fresh result id). This allows each
 // reduction step to increase the number of variables to check if the validator
@@ -101,7 +101,7 @@
   }
 
   std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
-      IRContext* context) const final {
+      IRContext* context, uint32_t /*unused*/) const final {
     std::vector<std::unique_ptr<ReductionOpportunity>> result;
     for (auto& function : *context->module()) {
       Instruction* first_instruction = &*function.begin()[0].begin();
diff --git a/test/tools/spirv_test_framework.py b/test/tools/spirv_test_framework.py
index 42f83c6..542f144 100755
--- a/test/tools/spirv_test_framework.py
+++ b/test/tools/spirv_test_framework.py
@@ -146,9 +146,9 @@
     # Some of our MacOS bots still run Python 2, so need to be backwards
     # compatible here.
     if type(stdout) is not str:     
-      if sys.version_info[0] is 2:
+      if sys.version_info[0] == 2:
        self.stdout = stdout.decode('utf-8')
-      elif sys.version_info[0] is 3:
+      elif sys.version_info[0] == 3:
         self.stdout = str(stdout, encoding='utf-8') if stdout is not None else stdout
       else:
         raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info))
@@ -156,9 +156,9 @@
       self.stdout = stdout
     
     if type(stderr) is not str:     
-      if sys.version_info[0] is 2:
+      if sys.version_info[0] == 2:
        self.stderr = stderr.decode('utf-8')
-      elif sys.version_info[0] is 3:
+      elif sys.version_info[0] == 3:
         self.stderr = str(stderr, encoding='utf-8') if stderr is not None else stderr
       else:
         raise Exception('Unable to determine if running Python 2 or 3 from {}'.format(sys.version_info))
diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt
index 23d7a19..153a916 100644
--- a/test/val/CMakeLists.txt
+++ b/test/val/CMakeLists.txt
@@ -41,21 +41,21 @@
        val_extension_spv_khr_terminate_invocation.cpp
        val_ext_inst_test.cpp
        ${VAL_TEST_COMMON_SRCS}
-  LIBS ${SPIRV_TOOLS}
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}
   PCH_FILE pch_test_val
 )
 
 add_spvtools_unittest(TARGET val_capability
   SRCS
        val_capability_test.cpp
-  LIBS ${SPIRV_TOOLS}
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}
   PCH_FILE pch_test_val
 )
 
 add_spvtools_unittest(TARGET val_limits
   SRCS val_limits_test.cpp
        ${VAL_TEST_COMMON_SRCS}
-  LIBS ${SPIRV_TOOLS}
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}
   PCH_FILE pch_test_val
 )
 
@@ -76,7 +76,7 @@
        val_opencl_test.cpp
        val_primitives_test.cpp
        ${VAL_TEST_COMMON_SRCS}
-  LIBS ${SPIRV_TOOLS}
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}
   PCH_FILE pch_test_val
 )
 
@@ -91,6 +91,6 @@
        val_version_test.cpp
        val_webgpu_test.cpp
        ${VAL_TEST_COMMON_SRCS}
-  LIBS ${SPIRV_TOOLS}
+  LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}
   PCH_FILE pch_test_val
 )
diff --git a/test/val/val_adjacency_test.cpp b/test/val/val_adjacency_test.cpp
index 0b09de0..2959853 100644
--- a/test/val/val_adjacency_test.cpp
+++ b/test/val/val_adjacency_test.cpp
@@ -324,7 +324,7 @@
 %false_label = OpLabel
 OpBranch %end_label
 %end_label = OpLabel
-%dummy = OpExtInst %void %extinst 123 %int_1
+%placeholder = OpExtInst %void %extinst 123 %int_1
 %result = OpPhi %bool %true %true_label %false %false_label
 )";
 
@@ -350,7 +350,7 @@
 OpBranch %end_label
 %end_label = OpLabel
 %result1 = OpPhi %bool %true %true_label %false %false_label
-%dummy = OpExtInst %void %extinst 123 %int_1
+%placeholder = OpExtInst %void %extinst 123 %int_1
 %result2 = OpPhi %bool %true %true_label %false %false_label
 )";
 
@@ -377,7 +377,7 @@
 %end_label = OpLabel
 OpLine %string 0 0
 %result = OpPhi %bool %true %true_label %false %false_label
-%dummy = OpExtInst %void %extinst 123 %int_1
+%placeholder = OpExtInst %void %extinst 123 %int_1
 )";
 
   const std::string extra = R"(OpCapability Shader
@@ -411,7 +411,7 @@
 %paramfunc_type = OpTypeFunction %void %int %int
 
 %paramfunc = OpFunction %void None %paramfunc_type
-%dummy = OpExtInst %void %extinst 123 %int_1
+%placeholder = OpExtInst %void %extinst 123 %int_1
 %a = OpFunctionParameter %int
 %b = OpFunctionParameter %int
 %paramfunc_entry = OpLabel
@@ -454,7 +454,7 @@
 
 %paramfunc = OpFunction %void None %paramfunc_type
 %a = OpFunctionParameter %int
-%dummy = OpExtInst %void %extinst 123 %int_1
+%placeholder = OpExtInst %void %extinst 123 %int_1
 %b = OpFunctionParameter %int
 %paramfunc_entry = OpLabel
 OpReturn
@@ -498,7 +498,7 @@
 %a = OpFunctionParameter %int
 %b = OpFunctionParameter %int
 %paramfunc_entry = OpLabel
-%dummy = OpExtInst %void %extinst 123 %int_1
+%placeholder = OpExtInst %void %extinst 123 %int_1
 OpReturn
 OpFunctionEnd
 
@@ -540,7 +540,7 @@
 OpReturn
 OpFunctionEnd
 
-%dummy = OpExtInst %void %extinst 123 %int_1
+%placeholder = OpExtInst %void %extinst 123 %int_1
 
 %main = OpFunction %void None %func
 %main_entry = OpLabel
diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp
index b1ab0c9..6bfcca3 100644
--- a/test/val/val_builtins_test.cpp
+++ b/test/val/val_builtins_test.cpp
@@ -1,4 +1,6 @@
 // Copyright (c) 2018 Google LLC.
+// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
+// reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -56,7 +58,7 @@
     std::tuple<const char*, const char*, const char*, const char*, TestResult>>;
 using ValidateVulkanCombineBuiltInExecutionModelDataTypeResult =
     spvtest::ValidateBase<std::tuple<const char*, const char*, const char*,
-                                     const char*, TestResult>>;
+                                     const char*, const char*, TestResult>>;
 using ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult =
     spvtest::ValidateBase<std::tuple<const char*, const char*, const char*,
                                      const char*, TestResult>>;
@@ -67,7 +69,12 @@
 using ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult =
     spvtest::ValidateBase<
         std::tuple<const char*, const char*, const char*, const char*,
-                   const char*, const char*, TestResult>>;
+                   const char*, const char*, const char*, TestResult>>;
+
+using ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult =
+    spvtest::ValidateBase<std::tuple<spv_target_env, const char*, const char*,
+                                     const char*, const char*, const char*,
+                                     const char*, const char*, TestResult>>;
 
 bool InitializerRequired(spv_target_env env, const char* const storage_class) {
   return spvIsWebGPUEnv(env) && (strncmp(storage_class, "Output", 6) == 0 ||
@@ -151,12 +158,40 @@
   return generator;
 }
 
+// Allows test parameter test to list all possible VUIDs with a delimiter that
+// is then split here to check if one VUID was in the error message
+MATCHER_P(AnyVUID, vuid_set, "VUID from the set is in error message") {
+  // use space as delimiter because clang-format will properly line break VUID
+  // strings which is important the entire VUID is in a single line for script
+  // to scan
+  std::string delimiter = " ";
+  std::string token;
+  std::string vuids = std::string(vuid_set);
+  size_t position;
+  do {
+    position = vuids.find(delimiter);
+    if (position != std::string::npos) {
+      token = vuids.substr(0, position);
+      vuids.erase(0, position + delimiter.length());
+    } else {
+      token = vuids.substr(0);  // last item
+    }
+
+    // arg contains diagnostic message
+    if (arg.find(token) != std::string::npos) {
+      return true;
+    }
+  } while (position != std::string::npos);
+  return false;
+}
+
 TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) {
   const char* const built_in = std::get<0>(GetParam());
   const char* const execution_model = std::get<1>(GetParam());
   const char* const storage_class = std::get<2>(GetParam());
   const char* const data_type = std::get<3>(GetParam());
-  const TestResult& test_result = std::get<4>(GetParam());
+  const char* const vuid = std::get<4>(GetParam());
+  const TestResult& test_result = std::get<5>(GetParam());
 
   CodeGenerator generator =
       GetInMainCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
@@ -171,6 +206,9 @@
   if (test_result.error_str2) {
     EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
   }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
 }
 
 TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InMain) {
@@ -204,7 +242,8 @@
   const char* const data_type = std::get<3>(GetParam());
   const char* const capabilities = std::get<4>(GetParam());
   const char* const extensions = std::get<5>(GetParam());
-  const TestResult& test_result = std::get<6>(GetParam());
+  const char* const vuid = std::get<6>(GetParam());
+  const TestResult& test_result = std::get<7>(GetParam());
 
   CodeGenerator generator = GetInMainCodeGenerator(
       SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
@@ -219,6 +258,39 @@
   if (test_result.error_str2) {
     EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
   }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
+}
+
+TEST_P(
+    ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    InMain) {
+  const spv_target_env env = std::get<0>(GetParam());
+  const char* const built_in = std::get<1>(GetParam());
+  const char* const execution_model = std::get<2>(GetParam());
+  const char* const storage_class = std::get<3>(GetParam());
+  const char* const data_type = std::get<4>(GetParam());
+  const char* const capabilities = std::get<5>(GetParam());
+  const char* const extensions = std::get<6>(GetParam());
+  const char* const vuid = std::get<7>(GetParam());
+  const TestResult& test_result = std::get<8>(GetParam());
+
+  CodeGenerator generator =
+      GetInMainCodeGenerator(env, built_in, execution_model, storage_class,
+                             capabilities, extensions, data_type);
+
+  CompileSuccessfully(generator.Build(), env);
+  ASSERT_EQ(test_result.validation_result, ValidateInstructions(env));
+  if (test_result.error_str) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str));
+  }
+  if (test_result.error_str2) {
+    EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
+  }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
 }
 
 CodeGenerator GetInFunctionCodeGenerator(spv_target_env env,
@@ -316,7 +388,8 @@
   const char* const execution_model = std::get<1>(GetParam());
   const char* const storage_class = std::get<2>(GetParam());
   const char* const data_type = std::get<3>(GetParam());
-  const TestResult& test_result = std::get<4>(GetParam());
+  const char* const vuid = std::get<4>(GetParam());
+  const TestResult& test_result = std::get<5>(GetParam());
 
   CodeGenerator generator =
       GetInFunctionCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
@@ -331,6 +404,9 @@
   if (test_result.error_str2) {
     EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
   }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
 }
 
 TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, InFunction) {
@@ -364,7 +440,8 @@
   const char* const data_type = std::get<3>(GetParam());
   const char* const capabilities = std::get<4>(GetParam());
   const char* const extensions = std::get<5>(GetParam());
-  const TestResult& test_result = std::get<6>(GetParam());
+  const char* const vuid = std::get<6>(GetParam());
+  const TestResult& test_result = std::get<7>(GetParam());
 
   CodeGenerator generator = GetInFunctionCodeGenerator(
       SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
@@ -379,6 +456,9 @@
   if (test_result.error_str2) {
     EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
   }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
 }
 
 CodeGenerator GetVariableCodeGenerator(spv_target_env env,
@@ -459,7 +539,8 @@
   const char* const execution_model = std::get<1>(GetParam());
   const char* const storage_class = std::get<2>(GetParam());
   const char* const data_type = std::get<3>(GetParam());
-  const TestResult& test_result = std::get<4>(GetParam());
+  const char* const vuid = std::get<4>(GetParam());
+  const TestResult& test_result = std::get<5>(GetParam());
 
   CodeGenerator generator =
       GetVariableCodeGenerator(SPV_ENV_VULKAN_1_0, built_in, execution_model,
@@ -474,6 +555,9 @@
   if (test_result.error_str2) {
     EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
   }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
 }
 
 TEST_P(ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult, Variable) {
@@ -507,7 +591,8 @@
   const char* const data_type = std::get<3>(GetParam());
   const char* const capabilities = std::get<4>(GetParam());
   const char* const extensions = std::get<5>(GetParam());
-  const TestResult& test_result = std::get<6>(GetParam());
+  const char* const vuid = std::get<6>(GetParam());
+  const TestResult& test_result = std::get<7>(GetParam());
 
   CodeGenerator generator = GetVariableCodeGenerator(
       SPV_ENV_VULKAN_1_0, built_in, execution_model, storage_class,
@@ -522,6 +607,9 @@
   if (test_result.error_str2) {
     EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2));
   }
+  if (vuid) {
+    EXPECT_THAT(getDiagnosticString(), AnyVUID(vuid));
+  }
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -530,7 +618,7 @@
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Output"), Values("%f32arr2", "%f32arr4"),
+            Values("Output"), Values("%f32arr2", "%f32arr4"), Values(nullptr),
             Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -539,14 +627,14 @@
     Combine(Values("ClipDistance", "CullDistance"),
             Values("Fragment", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Input"), Values("%f32arr2", "%f32arr4"),
+            Values("Input"), Values("%f32arr2", "%f32arr4"), Values(nullptr),
             Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     ClipAndCullDistanceFragmentOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
-            Values("Output"), Values("%f32arr4"),
+            Values("Output"), Values("%f32arr4"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance "
@@ -558,7 +646,7 @@
     VertexIdAndInstanceIdVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexId", "InstanceId"), Values("Vertex"), Values("Input"),
-            Values("%u32"),
+            Values("%u32"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow BuiltIn VertexId/InstanceId to be "
@@ -568,7 +656,7 @@
     ClipAndCullDistanceVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Vertex"),
-            Values("Input"), Values("%f32arr4"),
+            Values("Input"), Values("%f32arr4"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance "
@@ -581,6 +669,8 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("GLCompute"),
             Values("Input", "Output"), Values("%f32arr4"),
+            Values("VUID-ClipDistance-ClipDistance-04187 "
+                   "VUID-CullDistance-CullDistance-04196"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Fragment, Vertex, TessellationControl, "
@@ -591,6 +681,8 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f32vec2", "%f32vec4", "%f32"),
+            Values("VUID-ClipDistance-ClipDistance-04191 "
+                   "VUID-CullDistance-CullDistance-04200"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
                               "is not an array"))));
@@ -600,6 +692,8 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%u32arr2", "%u64arr4"),
+            Values("VUID-ClipDistance-ClipDistance-04191 "
+                   "VUID-CullDistance-CullDistance-04200"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
                               "components are not float scalar"))));
@@ -609,6 +703,8 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"),
             Values("Input"), Values("%f64arr2", "%f64arr4"),
+            Values("VUID-ClipDistance-ClipDistance-04191 "
+                   "VUID-CullDistance-CullDistance-04200"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float array",
                               "has components with bit width 64"))));
@@ -616,7 +712,7 @@
 INSTANTIATE_TEST_SUITE_P(
     FragCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec4"), Values(TestResult())));
+            Values("%f32vec4"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     FragCoordSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
@@ -631,6 +727,7 @@
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec4"),
+        Values("VUID-FragCoord-FragCoord-04210"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "to be used only with Fragment execution model"))));
 
@@ -646,7 +743,7 @@
 INSTANTIATE_TEST_SUITE_P(
     FragCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Output"),
-            Values("%f32vec4"),
+            Values("%f32vec4"), Values("VUID-FragCoord-FragCoord-04211"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -666,6 +763,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr4", "%u32vec4"),
+            Values("VUID-FragCoord-FragCoord-04212"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "is not a float vector"))));
@@ -683,7 +781,7 @@
     FragCoordNotFloatVec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec3"),
+            Values("%f32vec3"), Values("VUID-FragCoord-FragCoord-04212"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "has 3 components"))));
@@ -701,7 +799,7 @@
     FragCoordNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragCoord"), Values("Fragment"), Values("Input"),
-            Values("%f64vec4"),
+            Values("%f64vec4"), Values("VUID-FragCoord-FragCoord-04212"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "has components with bit width 64"))));
@@ -709,7 +807,7 @@
 INSTANTIATE_TEST_SUITE_P(
     FragDepthSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
-            Values("%f32"), Values(TestResult())));
+            Values("%f32"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     FragDepthSuccess, ValidateWebGPUCombineBuiltInExecutionModelDataTypeResult,
@@ -724,6 +822,7 @@
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "TessellationEvaluation"),
         Values("Output"), Values("%f32"),
+        Values("VUID-FragDepth-FragDepth-04213"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "to be used only with Fragment execution model"))));
 
@@ -740,7 +839,7 @@
     FragDepthNotOutput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Input"),
-            Values("%f32"),
+            Values("%f32"), Values("VUID-FragDepth-FragDepth-04214"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Output storage class",
@@ -761,6 +860,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
             Values("%f32vec4", "%u32"),
+            Values("VUID-FragDepth-FragDepth-04215"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
                               "is not a float scalar"))));
@@ -777,7 +877,7 @@
 INSTANTIATE_TEST_SUITE_P(
     FragDepthNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FragDepth"), Values("Fragment"), Values("Output"),
-            Values("%f64"),
+            Values("%f64"), Values("VUID-FragDepth-FragDepth-04215"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
                               "has bit width 64"))));
@@ -786,7 +886,8 @@
     FrontFacingAndHelperInvocationSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
-            Values("Input"), Values("%bool"), Values(TestResult())));
+            Values("Input"), Values("%bool"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     FrontFacingSuccess,
@@ -802,6 +903,8 @@
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "TessellationEvaluation"),
         Values("Input"), Values("%bool"),
+        Values("VUID-FrontFacing-FrontFacing-04229 "
+               "VUID-HelperInvocation-HelperInvocation-04239"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "to be used only with Fragment execution model"))));
 
@@ -819,6 +922,8 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
             Values("Output"), Values("%bool"),
+            Values("VUID-FrontFacing-FrontFacing-04230 "
+                   "VUID-HelperInvocation-HelperInvocation-04240"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -839,6 +944,8 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"),
             Values("Input"), Values("%f32", "%u32"),
+            Values("VUID-FrontFacing-FrontFacing-04231 "
+                   "VUID-HelperInvocation-HelperInvocation-04241"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a bool scalar",
                               "is not a bool scalar"))));
@@ -858,7 +965,7 @@
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
                    "WorkgroupId"),
             Values("GLCompute"), Values("Input"), Values("%u32vec3"),
-            Values(TestResult())));
+            Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     ComputeShaderInputInt32Vec3Success,
@@ -876,6 +983,10 @@
         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"))));
 
@@ -894,6 +1005,10 @@
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
                    "WorkgroupId"),
             Values("GLCompute"), Values("Output"), Values("%u32vec3"),
+            Values("VUID-GlobalInvocationId-GlobalInvocationId-04237 "
+                   "VUID-LocalInvocationId-LocalInvocationId-04282 "
+                   "VUID-NumWorkgroups-NumWorkgroups-04297 "
+                   "VUID-WorkgroupId-WorkgroupId-04423"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -916,6 +1031,10 @@
                    "WorkgroupId"),
             Values("GLCompute"), Values("Input"),
             Values("%u32arr3", "%f32vec3"),
+            Values("VUID-GlobalInvocationId-GlobalInvocationId-04238 "
+                   "VUID-LocalInvocationId-LocalInvocationId-04283 "
+                   "VUID-NumWorkgroups-NumWorkgroups-04298 "
+                   "VUID-WorkgroupId-WorkgroupId-04424"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
                               "is not an int vector"))));
@@ -936,6 +1055,10 @@
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
                    "WorkgroupId"),
             Values("GLCompute"), Values("Input"), Values("%u32vec4"),
+            Values("VUID-GlobalInvocationId-GlobalInvocationId-04238 "
+                   "VUID-LocalInvocationId-LocalInvocationId-04283 "
+                   "VUID-NumWorkgroups-NumWorkgroups-04298 "
+                   "VUID-WorkgroupId-WorkgroupId-04424"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
                               "has 4 components"))));
@@ -955,6 +1078,10 @@
     Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups",
                    "WorkgroupId"),
             Values("GLCompute"), Values("Input"), Values("%u64vec3"),
+            Values("VUID-GlobalInvocationId-GlobalInvocationId-04238 "
+                   "VUID-LocalInvocationId-LocalInvocationId-04283 "
+                   "VUID-NumWorkgroups-NumWorkgroups-04298 "
+                   "VUID-WorkgroupId-WorkgroupId-04424"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit int vector",
                               "has components with bit width 64"))));
@@ -963,7 +1090,8 @@
     InvocationIdSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
-            Values("Input"), Values("%u32"), Values(TestResult())));
+            Values("Input"), Values("%u32"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     InvocationIdInvalidExecutionModel,
@@ -971,6 +1099,7 @@
     Combine(Values("InvocationId"),
             Values("Vertex", "Fragment", "GLCompute", "TessellationEvaluation"),
             Values("Input"), Values("%u32"),
+            Values("VUID-InvocationId-InvocationId-04257"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
                               "Geometry execution models"))));
@@ -980,6 +1109,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
             Values("Output"), Values("%u32"),
+            Values("VUID-InvocationId-InvocationId-04258"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -990,6 +1120,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
             Values("Input"), Values("%f32", "%u32vec3"),
+            Values("VUID-InvocationId-InvocationId-04259"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
@@ -999,6 +1130,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"),
             Values("Input"), Values("%u64"),
+            Values("VUID-InvocationId-InvocationId-04259"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
@@ -1007,7 +1139,7 @@
     InstanceIndexSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
-            Values("%u32"), Values(TestResult())));
+            Values("%u32"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     InstanceIndexSuccess,
@@ -1022,6 +1154,7 @@
             Values("Geometry", "Fragment", "GLCompute", "TessellationControl",
                    "TessellationEvaluation"),
             Values("Input"), Values("%u32"),
+            Values("VUID-InstanceIndex-InstanceIndex-04263"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with Vertex execution model"))));
 
@@ -1037,7 +1170,7 @@
     InstanceIndexNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Output"),
-            Values("%u32"),
+            Values("%u32"), Values("VUID-InstanceIndex-InstanceIndex-04264"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -1058,6 +1191,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
             Values("%f32", "%u32vec3"),
+            Values("VUID-InstanceIndex-InstanceIndex-04265"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
@@ -1075,7 +1209,7 @@
     InstanceIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"),
-            Values("%u64"),
+            Values("%u64"), Values("VUID-InstanceIndex-InstanceIndex-04265"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
@@ -1084,41 +1218,52 @@
     LayerAndViewportIndexInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
-            Values("Input"), Values("%u32"), Values(TestResult())));
+            Values("Input"), Values("%u32"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Layer", "ViewportIndex"), Values("Geometry"),
-            Values("Output"), Values("%u32"), Values(TestResult())));
+            Values("Output"), Values("%u32"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("Layer", "ViewportIndex"),
-            Values("TessellationControl", "GLCompute"), Values("Input"),
-            Values("%u32"),
-            Values(TestResult(
-                SPV_ERROR_INVALID_DATA,
-                "to be used only with Vertex, TessellationEvaluation, "
-                "Geometry, or Fragment execution models"))));
+    Combine(
+        Values("Layer", "ViewportIndex"),
+        Values("TessellationControl", "GLCompute"), Values("Input"),
+        Values("%u32"),
+        Values("VUID-Layer-Layer-04272 VUID-ViewportIndex-ViewportIndex-04404"),
+        Values(
+            TestResult(SPV_ERROR_INVALID_DATA,
+                       "to be used only with Vertex, TessellationEvaluation, "
+                       "Geometry, or Fragment execution models"))));
 
 INSTANTIATE_TEST_SUITE_P(
-    LayerAndViewportIndexExecutionModelEnabledByCapability,
+    ViewportIndexExecutionModelEnabledByCapability,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("Layer", "ViewportIndex"),
-            Values("Vertex", "TessellationEvaluation"), Values("Output"),
-            Values("%u32"),
+    Combine(Values("ViewportIndex"), Values("Vertex", "TessellationEvaluation"),
+            Values("Output"), Values("%u32"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
-                "requires the ShaderViewportIndexLayerEXT capability"))));
+                "ShaderViewportIndexLayerEXT or ShaderViewportIndex"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    LayerExecutionModelEnabledByCapability,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
+    Combine(Values("Layer"), Values("Vertex", "TessellationEvaluation"),
+            Values("Output"), Values("%u32"), Values(nullptr),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "ShaderViewportIndexLayerEXT or ShaderLayer"))));
 
 INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexFragmentNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
         Values("Layer", "ViewportIndex"), Values("Fragment"), Values("Output"),
-        Values("%u32"),
+        Values("%u32"), Values(nullptr),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Output storage class if execution model is Fragment",
                           "which is called with execution model Fragment"))));
@@ -1129,7 +1274,7 @@
     Combine(
         Values("Layer", "ViewportIndex"),
         Values("Vertex", "TessellationEvaluation", "Geometry"), Values("Input"),
-        Values("%u32"),
+        Values("%u32"), Values(nullptr),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Input storage class if execution model is Vertex, "
                           "TessellationEvaluation, or Geometry",
@@ -1138,27 +1283,48 @@
 INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
-            Values("Input"), Values("%f32", "%u32vec3"),
-            Values(TestResult(SPV_ERROR_INVALID_DATA,
-                              "needs to be a 32-bit int scalar",
-                              "is not an int scalar"))));
+    Combine(
+        Values("Layer", "ViewportIndex"), Values("Fragment"), Values("Input"),
+        Values("%f32", "%u32vec3"),
+        Values("VUID-Layer-Layer-04276 VUID-ViewportIndex-ViewportIndex-04408"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 32-bit int scalar",
+                          "is not an int scalar"))));
 
 INSTANTIATE_TEST_SUITE_P(
     LayerAndViewportIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
-    Combine(Values("Layer", "ViewportIndex"), Values("Fragment"),
-            Values("Input"), Values("%u64"),
-            Values(TestResult(SPV_ERROR_INVALID_DATA,
-                              "needs to be a 32-bit int scalar",
-                              "has bit width 64"))));
+    Combine(
+        Values("Layer", "ViewportIndex"), Values("Fragment"), Values("Input"),
+        Values("%u64"),
+        Values("VUID-Layer-Layer-04276 VUID-ViewportIndex-ViewportIndex-04408"),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "needs to be a 32-bit int scalar",
+                          "has bit width 64"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    LayerCapability,
+    ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values(SPV_ENV_VULKAN_1_2), Values("Layer"), Values("Vertex"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability ShaderLayer\n"), Values(nullptr),
+            Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    ViewportIndexCapability,
+    ValidateGenericCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values(SPV_ENV_VULKAN_1_2), Values("ViewportIndex"),
+            Values("Vertex"), Values("Output"), Values("%u32"),
+            Values("OpCapability ShaderViewportIndex\n"), Values(nullptr),
+            Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PatchVerticesSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PatchVertices"),
             Values("TessellationEvaluation", "TessellationControl"),
-            Values("Input"), Values("%u32"), Values(TestResult())));
+            Values("Input"), Values("%u32"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PatchVerticesInvalidExecutionModel,
@@ -1166,6 +1332,7 @@
     Combine(Values("PatchVertices"),
             Values("Vertex", "Fragment", "GLCompute", "Geometry"),
             Values("Input"), Values("%u32"),
+            Values("VUID-PatchVertices-PatchVertices-04308"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
                               "TessellationEvaluation execution models"))));
@@ -1176,6 +1343,7 @@
     Combine(Values("PatchVertices"),
             Values("TessellationEvaluation", "TessellationControl"),
             Values("Output"), Values("%u32"),
+            Values("VUID-PatchVertices-PatchVertices-04309"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -1187,6 +1355,7 @@
     Combine(Values("PatchVertices"),
             Values("TessellationEvaluation", "TessellationControl"),
             Values("Input"), Values("%f32", "%u32vec3"),
+            Values("VUID-PatchVertices-PatchVertices-04310"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
@@ -1197,6 +1366,7 @@
     Combine(Values("PatchVertices"),
             Values("TessellationEvaluation", "TessellationControl"),
             Values("Input"), Values("%u64"),
+            Values("VUID-PatchVertices-PatchVertices-04310"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
@@ -1204,7 +1374,7 @@
 INSTANTIATE_TEST_SUITE_P(
     PointCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec2"), Values(TestResult())));
+            Values("%f32vec2"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PointCoordNotFragment,
@@ -1214,6 +1384,7 @@
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec2"),
+        Values("VUID-PointCoord-PointCoord-04311"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "to be used only with Fragment execution model"))));
 
@@ -1221,7 +1392,7 @@
     PointCoordNotInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Output"),
-            Values("%f32vec2"),
+            Values("%f32vec2"), Values("VUID-PointCoord-PointCoord-04312"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -1232,6 +1403,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr2", "%u32vec2"),
+            Values("VUID-PointCoord-PointCoord-04313"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
                               "is not a float vector"))));
@@ -1240,7 +1412,7 @@
     PointCoordNotFloatVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec3"),
+            Values("%f32vec3"), Values("VUID-PointCoord-PointCoord-04313"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
                               "has 3 components"))));
@@ -1249,7 +1421,7 @@
     PointCoordNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointCoord"), Values("Fragment"), Values("Input"),
-            Values("%f64vec2"),
+            Values("%f64vec2"), Values("VUID-PointCoord-PointCoord-04313"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
                               "has components with bit width 64"))));
@@ -1260,20 +1432,22 @@
     Combine(Values("PointSize"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Output"), Values("%f32"), Values(TestResult())));
+            Values("Output"), Values("%f32"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PointSizeInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
-            Values("Input"), Values("%f32"), Values(TestResult())));
+            Values("Input"), Values("%f32"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PointSizeVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Input"),
-            Values("%f32"),
+            Values("%f32"), Values("VUID-PointSize-PointSize-04315"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow BuiltIn PointSize "
@@ -1286,6 +1460,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("GLCompute", "Fragment"),
             Values("Input", "Output"), Values("%f32"),
+            Values("VUID-PointSize-PointSize-04314"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationControl, "
@@ -1296,6 +1471,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
             Values("%f32vec4", "%u32"),
+            Values("VUID-PointSize-PointSize-04317"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
                               "is not a float scalar"))));
@@ -1303,7 +1479,7 @@
 INSTANTIATE_TEST_SUITE_P(
     PointSizeNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PointSize"), Values("Vertex"), Values("Output"),
-            Values("%f64"),
+            Values("%f64"), Values("VUID-PointSize-PointSize-04317"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit float scalar",
                               "has bit width 64"))));
@@ -1314,7 +1490,8 @@
     Combine(Values("Position"),
             Values("Vertex", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
-            Values("Output"), Values("%f32vec4"), Values(TestResult())));
+            Values("Output"), Values("%f32vec4"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PositionOutputSuccess,
@@ -1336,7 +1513,8 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"),
             Values("Geometry", "TessellationControl", "TessellationEvaluation"),
-            Values("Input"), Values("%f32vec4"), Values(TestResult())));
+            Values("Input"), Values("%f32vec4"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PositionInputFailure,
@@ -1352,7 +1530,7 @@
     PositionVertexInput,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Vertex"), Values("Input"),
-            Values("%f32vec4"),
+            Values("%f32vec4"), Values("VUID-Position-Position-04320"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow BuiltIn Position "
@@ -1365,6 +1543,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("GLCompute", "Fragment"),
             Values("Input", "Output"), Values("%f32vec4"),
+            Values("VUID-Position-Position-04318"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Vertex, TessellationControl, "
@@ -1375,6 +1554,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
             Values("%f32arr4", "%u32vec4"),
+            Values("VUID-Position-Position-04321"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "is not a float vector"))));
@@ -1392,7 +1572,7 @@
     PositionNotFloatVec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
-            Values("%f32vec3"),
+            Values("%f32vec3"), Values("VUID-Position-Position-04321"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "has 3 components"))));
@@ -1410,7 +1590,7 @@
     PositionNotF32Vec4,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("Position"), Values("Geometry"), Values("Input"),
-            Values("%f64vec4"),
+            Values("%f64vec4"), Values("VUID-Position-Position-04321"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float vector",
                               "has components with bit width 64"))));
@@ -1421,19 +1601,21 @@
     Combine(Values("PrimitiveId"),
             Values("Fragment", "TessellationControl", "TessellationEvaluation",
                    "Geometry"),
-            Values("Input"), Values("%u32"), Values(TestResult())));
+            Values("Input"), Values("%u32"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Geometry"), Values("Output"),
-            Values("%u32"), Values(TestResult())));
+            Values("%u32"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     PrimitiveIdInvalidExecutionModel,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Vertex", "GLCompute"),
             Values("Input"), Values("%u32"),
+            Values("VUID-PrimitiveId-PrimitiveId-04330"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be used only with Fragment, TessellationControl, "
@@ -1444,7 +1626,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
         Values("PrimitiveId"), Values("Fragment"), Values("Output"),
-        Values("%u32"),
+        Values("%u32"), Values("VUID-PrimitiveId-PrimitiveId-04334"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Output storage class if execution model is Fragment",
                           "which is called with execution model Fragment"))));
@@ -1455,6 +1637,7 @@
     Combine(Values("PrimitiveId"),
             Values("TessellationControl", "TessellationEvaluation"),
             Values("Output"), Values("%u32"),
+            Values("VUID-PrimitiveId-PrimitiveId-04334"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Output storage class if execution model is Tessellation",
@@ -1465,6 +1648,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
+            Values("VUID-PrimitiveId-PrimitiveId-04337"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
@@ -1473,7 +1657,7 @@
     PrimitiveIdNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"),
-            Values("%u64"),
+            Values("%u64"), Values("VUID-PrimitiveId-PrimitiveId-04337"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
@@ -1481,7 +1665,7 @@
 INSTANTIATE_TEST_SUITE_P(
     SampleIdSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
-            Values("%u32"), Values(TestResult())));
+            Values("%u32"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     SampleIdInvalidExecutionModel,
@@ -1490,7 +1674,7 @@
         Values("SampleId"),
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "TessellationEvaluation"),
-        Values("Input"), Values("%u32"),
+        Values("Input"), Values("%u32"), Values("VUID-SampleId-SampleId-04354"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "to be used only with Fragment execution model"))));
 
@@ -1498,7 +1682,7 @@
     SampleIdNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
         Values("SampleId"), Values("Fragment"), Values("Output"),
-        Values("%u32"),
+        Values("%u32"), Values("VUID-SampleId-SampleId-04355"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Vulkan spec allows BuiltIn SampleId to be only used "
                           "for variables with Input storage class"))));
@@ -1507,7 +1691,7 @@
     SampleIdNotIntScalar,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
-            Values("%f32", "%u32vec3"),
+            Values("%f32", "%u32vec3"), Values("VUID-SampleId-SampleId-04356"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
@@ -1515,7 +1699,7 @@
 INSTANTIATE_TEST_SUITE_P(
     SampleIdNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleId"), Values("Fragment"), Values("Input"),
-            Values("%u64"),
+            Values("%u64"), Values("VUID-SampleId-SampleId-04356"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
@@ -1523,7 +1707,8 @@
 INSTANTIATE_TEST_SUITE_P(
     SampleMaskSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input", "Output"),
-            Values("%u32arr2", "%u32arr4"), Values(TestResult())));
+            Values("%u32arr2", "%u32arr4"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     SampleMaskInvalidExecutionModel,
@@ -1533,6 +1718,7 @@
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "TessellationEvaluation"),
         Values("Input"), Values("%u32arr2"),
+        Values("VUID-SampleMask-SampleMask-04357"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "to be used only with Fragment execution model"))));
 
@@ -1540,7 +1726,7 @@
     SampleMaskWrongStorageClass,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"),
-            Values("%u32arr2"),
+            Values("%u32arr2"), Values("VUID-SampleMask-SampleMask-04358"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec allows BuiltIn SampleMask to be only used for "
@@ -1551,6 +1737,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
             Values("%f32", "%u32vec3"),
+            Values("VUID-SampleMask-SampleMask-04359"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
                               "is not an array"))));
@@ -1559,7 +1746,7 @@
     SampleMaskNotIntArray,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
-            Values("%f32arr2"),
+            Values("%f32arr2"), Values("VUID-SampleMask-SampleMask-04359"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
                               "components are not int scalar"))));
@@ -1568,7 +1755,7 @@
     SampleMaskNotInt32Array,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SampleMask"), Values("Fragment"), Values("Input"),
-            Values("%u64arr2"),
+            Values("%u64arr2"), Values("VUID-SampleMask-SampleMask-04359"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int array",
                               "has components with bit width 64"))));
@@ -1577,7 +1764,7 @@
     SamplePositionSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
-            Values("%f32vec2"), Values(TestResult())));
+            Values("%f32vec2"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     SamplePositionNotFragment,
@@ -1587,6 +1774,7 @@
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "TessellationEvaluation"),
         Values("Input"), Values("%f32vec2"),
+        Values("VUID-SamplePosition-SamplePosition-04360"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "to be used only with Fragment execution model"))));
 
@@ -1595,6 +1783,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Output"),
             Values("%f32vec2"),
+            Values("VUID-SamplePosition-SamplePosition-04361"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -1605,6 +1794,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f32arr2", "%u32vec4"),
+            Values("VUID-SamplePosition-SamplePosition-04362"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
                               "is not a float vector"))));
@@ -1614,6 +1804,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f32vec3"),
+            Values("VUID-SamplePosition-SamplePosition-04362"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
                               "has 3 components"))));
@@ -1623,6 +1814,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"),
             Values("%f64vec2"),
+            Values("VUID-SamplePosition-SamplePosition-04362"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float vector",
                               "has components with bit width 64"))));
@@ -1630,7 +1822,8 @@
 INSTANTIATE_TEST_SUITE_P(
     TessCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32vec3"), Values(TestResult())));
+            Values("Input"), Values("%f32vec3"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     TessCoordNotFragment,
@@ -1640,6 +1833,7 @@
         Values("Vertex", "GLCompute", "Geometry", "TessellationControl",
                "Fragment"),
         Values("Input"), Values("%f32vec3"),
+        Values("VUID-TessCoord-TessCoord-04387"),
         Values(TestResult(
             SPV_ERROR_INVALID_DATA,
             "to be used only with TessellationEvaluation execution model"))));
@@ -1647,7 +1841,7 @@
 INSTANTIATE_TEST_SUITE_P(
     TessCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Output"),
-            Values("%f32vec3"),
+            Values("%f32vec3"), Values("VUID-TessCoord-TessCoord-04388"),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -1658,6 +1852,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
             Values("%f32arr3", "%u32vec4"),
+            Values("VUID-TessCoord-TessCoord-04389"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
                               "is not a float vector"))));
@@ -1666,7 +1861,7 @@
     TessCoordNotFloatVec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
-            Values("%f32vec2"),
+            Values("%f32vec2"), Values("VUID-TessCoord-TessCoord-04389"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
                               "has 2 components"))));
@@ -1675,7 +1870,7 @@
     TessCoordNotF32Vec3,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessCoord"), Values("Fragment"), Values("Input"),
-            Values("%f64vec3"),
+            Values("%f64vec3"), Values("VUID-TessCoord-TessCoord-04389"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 3-component 32-bit float vector",
                               "has components with bit width 64"))));
@@ -1684,13 +1879,15 @@
     TessLevelOuterTeseInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32arr4"), Values(TestResult())));
+            Values("Input"), Values("%f32arr4"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterTescOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationControl"),
-            Values("Output"), Values("%f32arr4"), Values(TestResult())));
+            Values("Output"), Values("%f32arr4"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     TessLevelOuterInvalidExecutionModel,
@@ -1698,6 +1895,7 @@
     Combine(Values("TessLevelOuter"),
             Values("Vertex", "GLCompute", "Geometry", "Fragment"),
             Values("Input"), Values("%f32arr4"),
+            Values("VUID-TessLevelOuter-TessLevelOuter-04390"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
                               "TessellationEvaluation execution models."))));
@@ -1706,7 +1904,7 @@
     TessLevelOuterOutputTese,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
-            Values("Output"), Values("%f32arr4"),
+            Values("Output"), Values("%f32arr4"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
@@ -1717,7 +1915,7 @@
     TessLevelOuterInputTesc,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationControl"),
-            Values("Input"), Values("%f32arr4"),
+            Values("Input"), Values("%f32arr4"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
@@ -1729,6 +1927,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32vec4", "%f32"),
+            Values("VUID-TessLevelOuter-TessLevelOuter-04393"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
                               "is not an array"))));
@@ -1738,6 +1937,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%u32arr4"),
+            Values("VUID-TessLevelOuter-TessLevelOuter-04393"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
                               "components are not float scalar"))));
@@ -1747,6 +1947,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32arr3"),
+            Values("VUID-TessLevelOuter-TessLevelOuter-04393"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
                               "has 3 components"))));
@@ -1756,6 +1957,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f64arr4"),
+            Values("VUID-TessLevelOuter-TessLevelOuter-04393"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 4-component 32-bit float array",
                               "has components with bit width 64"))));
@@ -1764,13 +1966,15 @@
     TessLevelInnerTeseInputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
-            Values("Input"), Values("%f32arr2"), Values(TestResult())));
+            Values("Input"), Values("%f32arr2"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerTescOutputSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationControl"),
-            Values("Output"), Values("%f32arr2"), Values(TestResult())));
+            Values("Output"), Values("%f32arr2"), Values(nullptr),
+            Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     TessLevelInnerInvalidExecutionModel,
@@ -1778,6 +1982,7 @@
     Combine(Values("TessLevelInner"),
             Values("Vertex", "GLCompute", "Geometry", "Fragment"),
             Values("Input"), Values("%f32arr2"),
+            Values("VUID-TessLevelInner-TessLevelInner-04394"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with TessellationControl or "
                               "TessellationEvaluation execution models."))));
@@ -1786,7 +1991,7 @@
     TessLevelInnerOutputTese,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
-            Values("Output"), Values("%f32arr2"),
+            Values("Output"), Values("%f32arr2"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
@@ -1797,7 +2002,7 @@
     TessLevelInnerInputTesc,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationControl"),
-            Values("Input"), Values("%f32arr2"),
+            Values("Input"), Values("%f32arr2"), Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be "
@@ -1809,6 +2014,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32vec2", "%f32"),
+            Values("VUID-TessLevelInner-TessLevelInner-04397"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
                               "is not an array"))));
@@ -1818,6 +2024,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%u32arr2"),
+            Values("VUID-TessLevelInner-TessLevelInner-04397"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
                               "components are not float scalar"))));
@@ -1827,6 +2034,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f32arr3"),
+            Values("VUID-TessLevelInner-TessLevelInner-04397"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
                               "has 3 components"))));
@@ -1836,6 +2044,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("TessLevelInner"), Values("TessellationEvaluation"),
             Values("Input"), Values("%f64arr2"),
+            Values("VUID-TessLevelInner-TessLevelInner-04397"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 2-component 32-bit float array",
                               "has components with bit width 64"))));
@@ -1844,7 +2053,7 @@
     VertexIndexSuccess,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
-            Values("%u32"), Values(TestResult())));
+            Values("%u32"), Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     VertexIndexSuccess,
@@ -1859,6 +2068,7 @@
             Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
                    "TessellationEvaluation"),
             Values("Input"), Values("%u32"),
+            Values("VUID-VertexIndex-VertexIndex-04398"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "to be used only with Vertex execution model"))));
 
@@ -1875,7 +2085,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(
         Values("VertexIndex"), Values("Vertex"), Values("Output"),
-        Values("%u32"),
+        Values("%u32"), Values("VUID-VertexIndex-VertexIndex-04399"),
         Values(TestResult(SPV_ERROR_INVALID_DATA,
                           "Vulkan spec allows BuiltIn VertexIndex to be only "
                           "used for variables with Input storage class"))));
@@ -1895,6 +2105,7 @@
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
             Values("%f32", "%u32vec3"),
+            Values("VUID-VertexIndex-VertexIndex-04400"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
@@ -1912,7 +2123,7 @@
     VertexIndexNotInt32,
     ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
     Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"),
-            Values("%u64"),
+            Values("%u64"), Values("VUID-VertexIndex-VertexIndex-04400"),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
@@ -1961,6 +2172,183 @@
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "WebGPU does not allow BuiltIn"))));
 
+INSTANTIATE_TEST_SUITE_P(
+    BaseInstanceOrVertexSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("BaseInstance", "BaseVertex"), Values("Vertex"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    BaseInstanceOrVertexInvalidExecutionModel,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("BaseInstance", "BaseVertex"),
+            Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
+                   "TessellationEvaluation"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values("VUID-BaseInstance-BaseInstance-04181 "
+                   "VUID-BaseVertex-BaseVertex-04184"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    BaseInstanceOrVertexNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("BaseInstance", "BaseVertex"), Values("Vertex"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values("VUID-BaseInstance-BaseInstance-04182 "
+                   "VUID-BaseVertex-BaseVertex-04185"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows",
+                              "used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    BaseInstanceOrVertexNotIntScalar,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("BaseInstance", "BaseVertex"), Values("Vertex"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values("VUID-BaseInstance-BaseInstance-04183 "
+                   "VUID-BaseVertex-BaseVertex-04186"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    DrawIndexSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("DrawIndex"), Values("Vertex"), Values("Input"),
+            Values("%u32"), Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    DrawIndexMeshSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("DrawIndex"), Values("MeshNV", "TaskNV"), Values("Input"),
+        Values("%u32"), Values("OpCapability MeshShadingNV\n"),
+        Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\nOpExtension "
+               "\"SPV_NV_mesh_shader\"\n"),
+        Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    DrawIndexInvalidExecutionModel,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("DrawIndex"),
+            Values("Fragment", "GLCompute", "Geometry", "TessellationControl",
+                   "TessellationEvaluation"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values("VUID-DrawIndex-DrawIndex-04207"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "to be used only with Vertex, MeshNV, or TaskNV "
+                              "execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    DrawIndexNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("DrawIndex"), Values("Vertex"), Values("Output"),
+            Values("%u32"), Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values("VUID-DrawIndex-DrawIndex-04208"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows",
+                              "used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    DrawIndexNotIntScalar,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("DrawIndex"), Values("Vertex"), Values("Input"),
+            Values("%f32", "%u32vec3"), Values("OpCapability DrawParameters\n"),
+            Values("OpExtension \"SPV_KHR_shader_draw_parameters\"\n"),
+            Values("VUID-DrawIndex-DrawIndex-04209"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ViewIndexSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("ViewIndex"),
+            Values("Fragment", "Vertex", "Geometry", "TessellationControl",
+                   "TessellationEvaluation"),
+            Values("Input"), Values("%u32"), Values("OpCapability MultiView\n"),
+            Values("OpExtension \"SPV_KHR_multiview\"\n"), Values(nullptr),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    ViewIndexInvalidExecutionModel,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("ViewIndex"), Values("GLCompute"), Values("Input"),
+            Values("%u32"), Values("OpCapability MultiView\n"),
+            Values("OpExtension \"SPV_KHR_multiview\"\n"),
+            Values("VUID-ViewIndex-ViewIndex-04401"),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "to be not be used with GLCompute execution model"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ViewIndexNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("ViewIndex"), Values("Vertex"), Values("Output"),
+            Values("%u32"), Values("OpCapability MultiView\n"),
+            Values("OpExtension \"SPV_KHR_multiview\"\n"),
+            Values("VUID-ViewIndex-ViewIndex-04402"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows",
+                              "used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ViewIndexNotIntScalar,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("ViewIndex"), Values("Vertex"), Values("Input"),
+            Values("%f32", "%u32vec3"), Values("OpCapability MultiView\n"),
+            Values("OpExtension \"SPV_KHR_multiview\"\n"),
+            Values("VUID-ViewIndex-ViewIndex-04403"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    DeviceIndexSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("DeviceIndex"),
+            Values("Fragment", "Vertex", "Geometry", "TessellationControl",
+                   "TessellationEvaluation", "GLCompute"),
+            Values("Input"), Values("%u32"),
+            Values("OpCapability DeviceGroup\n"),
+            Values("OpExtension \"SPV_KHR_device_group\"\n"), Values(nullptr),
+            Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    DeviceIndexNotInput,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("DeviceIndex"), Values("Fragment", "Vertex", "GLCompute"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability DeviceGroup\n"),
+            Values("OpExtension \"SPV_KHR_device_group\"\n"),
+            Values("VUID-DeviceIndex-DeviceIndex-04205"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA, "Vulkan spec allows",
+                              "used for variables with Input storage class"))));
+
+INSTANTIATE_TEST_SUITE_P(
+    DeviceIndexNotIntScalar,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("DeviceIndex"), Values("Fragment", "Vertex", "GLCompute"),
+            Values("Input"), Values("%f32", "%u32vec3"),
+            Values("OpCapability DeviceGroup\n"),
+            Values("OpExtension \"SPV_KHR_device_group\"\n"),
+            Values("VUID-DeviceIndex-DeviceIndex-04206"),
+            Values(TestResult(SPV_ERROR_INVALID_DATA,
+                              "needs to be a 32-bit int scalar",
+                              "is not an int scalar"))));
+
 CodeGenerator GetArrayedVariableCodeGenerator(spv_target_env env,
                                               const char* const built_in,
                                               const char* const execution_model,
@@ -2153,7 +2541,7 @@
             Values("Input"), Values("%u32"),
             Values("OpCapability ShaderSMBuiltinsNV\n"),
             Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
-            Values(TestResult())));
+            Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     SMBuiltinsInputMeshSuccess,
@@ -2164,7 +2552,7 @@
         Values("OpCapability ShaderSMBuiltinsNV\nOpCapability MeshShadingNV\n"),
         Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension "
                "\"SPV_NV_mesh_shader\"\n"),
-        Values(TestResult())));
+        Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     SMBuiltinsInputRaySuccess,
@@ -2177,7 +2565,7 @@
         Values("OpCapability ShaderSMBuiltinsNV\nOpCapability RayTracingNV\n"),
         Values("OpExtension \"SPV_NV_shader_sm_builtins\"\nOpExtension "
                "\"SPV_NV_ray_tracing\"\n"),
-        Values(TestResult())));
+        Values(nullptr), Values(TestResult())));
 
 INSTANTIATE_TEST_SUITE_P(
     SMBuiltinsNotInput,
@@ -2188,6 +2576,7 @@
             Values("Output"), Values("%u32"),
             Values("OpCapability ShaderSMBuiltinsNV\n"),
             Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(nullptr),
             Values(TestResult(
                 SPV_ERROR_INVALID_DATA,
                 "to be only used for variables with Input storage class",
@@ -2202,6 +2591,7 @@
             Values("Input"), Values("%f32", "%u32vec3"),
             Values("OpCapability ShaderSMBuiltinsNV\n"),
             Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(nullptr),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "is not an int scalar"))));
@@ -2215,6 +2605,7 @@
             Values("Input"), Values("%u64"),
             Values("OpCapability ShaderSMBuiltinsNV\n"),
             Values("OpExtension \"SPV_NV_shader_sm_builtins\"\n"),
+            Values(nullptr),
             Values(TestResult(SPV_ERROR_INVALID_DATA,
                               "needs to be a 32-bit int scalar",
                               "has bit width 64"))));
@@ -2294,6 +2685,9 @@
               HasSubstr("is referencing ID <2> (OpConstantComposite) which is "
                         "decorated with BuiltIn WorkgroupSize in function <1> "
                         "called with execution model Fragment"));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04425 "
+                      "VUID-WorkgroupSize-WorkgroupSize-04427"));
 }
 
 TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeFragment) {
@@ -2369,6 +2763,8 @@
               HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize "
                         "variable needs to be a 3-component 32-bit int vector. "
                         "ID <2> (OpConstant) is not an int vector."));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427"));
 }
 
 TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVector) {
@@ -2417,6 +2813,8 @@
               HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize "
                         "variable needs to be a 3-component 32-bit int vector. "
                         "ID <2> (OpConstantComposite) is not an int vector."));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427"));
 }
 
 TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotIntVector) {
@@ -2465,6 +2863,8 @@
               HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize "
                         "variable needs to be a 3-component 32-bit int vector. "
                         "ID <2> (OpConstantComposite) has 2 components."));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427"));
 }
 
 TEST_F(ValidateBuiltIns, WebGPUWorkgroupSizeNotVec3) {
@@ -2503,6 +2903,8 @@
       HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize variable "
                 "needs to be a 3-component 32-bit int vector. ID <2> "
                 "(OpConstantComposite) has components with bit width 64."));
+  EXPECT_THAT(getDiagnosticString(),
+              AnyVUID("VUID-WorkgroupSize-WorkgroupSize-04427"));
 }
 
 TEST_F(ValidateBuiltIns, WorkgroupSizePrivateVar) {
@@ -2806,6 +3208,8 @@
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Vulkan spec requires DepthReplacing execution mode to "
                         "be declared when using BuiltIn FragDepth"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("VUID-FragDepth-FragDepth-04216"));
 }
 
 TEST_F(ValidateBuiltIns, WebGPUFragmentFragDepthNoDepthReplacing) {
@@ -2885,6 +3289,8 @@
   EXPECT_THAT(getDiagnosticString(),
               HasSubstr("Vulkan spec requires DepthReplacing execution mode to "
                         "be declared when using BuiltIn FragDepth"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("VUID-FragDepth-FragDepth-04216"));
 }
 
 TEST_F(ValidateBuiltIns,
@@ -3377,6 +3783,108 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
 }
 
+INSTANTIATE_TEST_SUITE_P(
+    PrimitiveShadingRateOutputSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("PrimitiveShadingRateKHR"), Values("Vertex", "Geometry"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability FragmentShadingRateKHR\n"),
+            Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+            Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    PrimitiveShadingRateMeshOutputSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("PrimitiveShadingRateKHR"), Values("MeshNV"),
+            Values("Output"), Values("%u32"),
+            Values("OpCapability FragmentShadingRateKHR\nOpCapability "
+                   "MeshShadingNV\n"),
+            Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\nOpExtension "
+                   "\"SPV_NV_mesh_shader\"\n"),
+            Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    PrimitiveShadingRateInvalidExecutionModel,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("PrimitiveShadingRateKHR"), Values("Fragment"), Values("Output"),
+        Values("%u32"), Values("OpCapability FragmentShadingRateKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+        Values("VUID-PrimitiveShadingRateKHR-PrimitiveShadingRateKHR-04484 "),
+        Values(TestResult(
+            SPV_ERROR_INVALID_DATA,
+            "Vulkan spec allows BuiltIn PrimitiveShadingRateKHR to be used "
+            "only with Vertex, Geometry, or MeshNV execution models."))));
+
+INSTANTIATE_TEST_SUITE_P(
+    PrimitiveShadingRateInvalidStorageClass,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("PrimitiveShadingRateKHR"), Values("Vertex"), Values("Input"),
+        Values("%u32"), Values("OpCapability FragmentShadingRateKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+        Values("VUID-PrimitiveShadingRateKHR-PrimitiveShadingRateKHR-04485 "),
+        Values(TestResult(
+            SPV_ERROR_INVALID_DATA,
+            "Vulkan spec allows BuiltIn PrimitiveShadingRateKHR to be only "
+            "used for variables with Output storage class."))));
+
+INSTANTIATE_TEST_SUITE_P(
+    PrimitiveShadingRateInvalidType,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("PrimitiveShadingRateKHR"), Values("Vertex"), Values("Output"),
+        Values("%f32"), Values("OpCapability FragmentShadingRateKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+        Values("VUID-PrimitiveShadingRateKHR-PrimitiveShadingRateKHR-04485 "),
+        Values(TestResult(
+            SPV_ERROR_INVALID_DATA,
+            "According to the Vulkan spec BuiltIn PrimitiveShadingRateKHR "
+            "variable needs to be a 32-bit int scalar."))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ShadingRateInputSuccess,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("ShadingRateKHR"), Values("Fragment"), Values("Input"),
+            Values("%u32"), Values("OpCapability FragmentShadingRateKHR\n"),
+            Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+            Values(nullptr), Values(TestResult())));
+
+INSTANTIATE_TEST_SUITE_P(
+    ShadingRateInvalidExecutionModel,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("ShadingRateKHR"), Values("Vertex"), Values("Input"),
+            Values("%u32"), Values("OpCapability FragmentShadingRateKHR\n"),
+            Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+            Values("VUID-ShadingRateKHR-ShadingRateKHR-04490 "),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "Vulkan spec allows BuiltIn ShadingRateKHR to be used "
+                "only with the Fragment execution model."))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ShadingRateInvalidStorageClass,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(Values("ShadingRateKHR"), Values("Fragment"), Values("Output"),
+            Values("%u32"), Values("OpCapability FragmentShadingRateKHR\n"),
+            Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+            Values("VUID-ShadingRateKHR-ShadingRateKHR-04491 "),
+            Values(TestResult(
+                SPV_ERROR_INVALID_DATA,
+                "Vulkan spec allows BuiltIn ShadingRateKHR to be only "
+                "used for variables with Input storage class."))));
+
+INSTANTIATE_TEST_SUITE_P(
+    ShadingRateInvalidType,
+    ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult,
+    Combine(
+        Values("ShadingRateKHR"), Values("Fragment"), Values("Input"),
+        Values("%f32"), Values("OpCapability FragmentShadingRateKHR\n"),
+        Values("OpExtension \"SPV_KHR_fragment_shading_rate\"\n"),
+        Values("VUID-ShadingRateKHR-ShadingRateKHR-04492 "),
+        Values(TestResult(SPV_ERROR_INVALID_DATA,
+                          "According to the Vulkan spec BuiltIn ShadingRateKHR "
+                          "variable needs to be a 32-bit int scalar."))));
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp
index 756f762..9705cb8 100644
--- a/test/val/val_capability_test.cpp
+++ b/test/val/val_capability_test.cpp
@@ -2288,11 +2288,30 @@
                         "Embedded Profile"));
 }
 
+TEST_F(ValidateCapability, OpenCL12EmbeddedNoLongerEnabledByCapability) {
+  const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Addresses
+OpCapability Linkage
+OpCapability Pipes
+OpMemoryModel Physical64 OpenCL
+%u32    = OpTypeInt 32 0
+)" + std::string(kVoidFVoid);
+
+  CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_1_2);
+  EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY,
+            ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_1_2));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Capability Pipes is not allowed by OpenCL 1.2 "
+                        "Embedded Profile"));
+}
+
 TEST_F(ValidateCapability, OpenCL20FullCapability) {
   const std::string spirv = R"(
 OpCapability Kernel
 OpCapability Addresses
 OpCapability Linkage
+OpCapability Groups
 OpCapability Pipes
 OpMemoryModel Physical64 OpenCL
 %u32    = OpTypeInt 32 0
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 630a19d..b4d1c28 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -1063,10 +1063,10 @@
   Block merge("merge", SpvOpUnreachable);
 
   entry.AppendBody(spvIsWebGPUEnv(env)
-                       ? "%dummy   = OpVariable %intptrt Function %two\n"
-                       : "%dummy   = OpVariable %intptrt Function\n");
+                       ? "%placeholder   = OpVariable %intptrt Function %two\n"
+                       : "%placeholder   = OpVariable %intptrt Function\n");
   entry.AppendBody("%cond    = OpSLessThan %boolt %one %two\n");
-  merge.AppendBody("OpStore %dummy %one\n");
+  merge.AppendBody("OpStore %placeholder %one\n");
 
   std::string str = header;
   if (spvIsWebGPUEnv(env)) {
@@ -1120,9 +1120,9 @@
   target >> branch;
 
   entry.AppendBody(spvIsWebGPUEnv(env)
-                       ? "%dummy   = OpVariable %intptrt Function %two\n"
-                       : "%dummy   = OpVariable %intptrt Function\n");
-  target.AppendBody("OpStore %dummy %one\n");
+                       ? "%placeholder   = OpVariable %intptrt Function %two\n"
+                       : "%placeholder   = OpVariable %intptrt Function\n");
+  target.AppendBody("OpStore %placeholder %one\n");
 
   std::string str = header;
   if (spvIsWebGPUEnv(env)) {
@@ -1279,8 +1279,8 @@
   target >> branch;
 
   entry.AppendBody(spvIsWebGPUEnv(env)
-                       ? "%dummy   = OpVariable %intptrt Function %two\n"
-                       : "%dummy   = OpVariable %intptrt Function\n");
+                       ? "%placeholder   = OpVariable %intptrt Function %two\n"
+                       : "%placeholder   = OpVariable %intptrt Function\n");
 
   std::string str = header;
   if (spvIsWebGPUEnv(env)) {
@@ -4565,6 +4565,39 @@
   ASSERT_FALSE(b11->reachable());
 }
 
+TEST_F(ValidateCFG, PhiInstructionWithDuplicateIncomingEdges) {
+  const std::string text = 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 %10 None
+               OpBranchConditional %7 %8 %9
+          %8 = OpLabel
+               OpBranch %10
+          %9 = OpLabel
+	       OpBranch %10
+         %10 = OpLabel
+	 %11 = OpPhi %6 %7 %8 %7 %8
+               OpReturn
+               OpFunctionEnd
+)";
+
+  CompileSuccessfully(text);
+  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("OpPhi references incoming basic block <id> "));
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("multiple times."));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index d8d0010..683a76f 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -54,8 +54,12 @@
     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>;
@@ -83,6 +87,7 @@
 using ValidateOpenCLStdFrexpLike = spvtest::ValidateBase<std::string>;
 using ValidateOpenCLStdLdexpLike = spvtest::ValidateBase<std::string>;
 using ValidateOpenCLStdUpsampleLike = spvtest::ValidateBase<std::string>;
+using ValidateClspvReflection = spvtest::ValidateBase<bool>;
 
 // Returns number of components in Pack/Unpack extended instructions.
 // |ext_inst_name| is expected to be of the format "PackHalf2x16".
@@ -755,9 +760,8 @@
   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
-%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified
 %int_info = OpExtInst %void %DbgExt DebugTypeBasic %int_name %u32_0 Signed
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info
+%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
@@ -800,8 +804,7 @@
   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
-%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info
+%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
 )";
 
@@ -831,8 +834,7 @@
 %dbgNone = OpExtInst %void %DbgExt DebugInfoNone
 %dbg_src = OpExtInst %void %DbgExt DebugSource %src %code
 %comp_unit = OpExtInst %void %DbgExt DebugCompilationUnit 2 4 %dbg_src HLSL
-%void_info = OpExtInst %void %DbgExt DebugTypeBasic %void_name %u32_0 Unspecified
-%main_type_info = OpExtInst %void %DbgExt DebugTypeFunction FlagIsPublic %void_info %void_info
+%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
 )";
 
@@ -1343,6 +1345,40 @@
   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"
@@ -1399,8 +1435,10 @@
       src, size_const, dbg_inst_header, "", extension, "Vertex"));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("expected operand Component Count must be a result id "
-                        "of OpConstant"));
+              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) {
@@ -1429,7 +1467,10 @@
       src, size_const, dbg_inst_header, "", extension, "Vertex"));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Component Count must be positive integer"));
+              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) {
@@ -1458,7 +1499,47 @@
       src, size_const, dbg_inst_header, "", extension, "Vertex"));
   ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Component Count must be positive integer"));
+              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) {
@@ -2602,6 +2683,581 @@
           "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 =
@@ -7979,6 +8635,703 @@
                              "s_upsample",
                          }));
 
+TEST_F(ValidateClspvReflection, RequiresNonSemanticExtension) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NonSemantic extended instruction sets cannot be "
+                        "declared without SPV_KHR_non_semantic_info"));
+}
+
+TEST_F(ValidateClspvReflection, MissingVersion) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.ClspvReflection."
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 1
+%5 = OpExtInst %2 %1 SpecConstantWorkDim %4
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Missing NonSemantic.ClspvReflection import version"));
+}
+
+TEST_F(ValidateClspvReflection, BadVersion0) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.ClspvReflection.0"
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 1
+%5 = OpExtInst %2 %1 SpecConstantWorkDim %4
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Unknown NonSemantic.ClspvReflection import version"));
+}
+
+TEST_F(ValidateClspvReflection, BadVersionNotANumber) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_non_semantic_info"
+%1 = OpExtInstImport "NonSemantic.ClspvReflection.1a"
+OpMemoryModel Logical GLSL450
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpConstant %3 1
+%5 = OpExtInst %2 %1 SpecConstantWorkDim %4
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("NonSemantic.ClspvReflection import does not encode "
+                        "the version correctly"));
+}
+
+TEST_F(ValidateClspvReflection, Kernel) {
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateClspvReflection, KernelNotAFunction) {
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo_name %foo_name
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Kernel does not reference a function"));
+}
+
+TEST_F(ValidateClspvReflection, KernelNotAnEntryPoint) {
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%bar = OpFunction %void None %void_fn
+%bar_entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %bar %foo_name
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Kernel does not reference an entry-point"));
+}
+
+TEST_F(ValidateClspvReflection, KernelNotGLCompute) {
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %foo "foo"
+OpExecutionMode %foo OriginUpperLeft
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Kernel must refer only to GLCompute entry-points"));
+}
+
+TEST_F(ValidateClspvReflection, KernelNameMismatch) {
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "bar"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+)";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Name must match an entry-point for Kernel"));
+}
+
+using ArgumentBasics =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateClspvReflectionArgumentKernel, ArgumentBasics,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair("ArgumentStorageBuffer", "%int_0 %int_0"),
+        std::make_pair("ArgumentUniform", "%int_0 %int_0"),
+        std::make_pair("ArgumentPodStorageBuffer",
+                       "%int_0 %int_0 %int_0 %int_4"),
+        std::make_pair("ArgumentPodUniform", "%int_0 %int_0 %int_0 %int_4"),
+        std::make_pair("ArgumentPodPushConstant", "%int_0 %int_4"),
+        std::make_pair("ArgumentSampledImage", "%int_0 %int_0"),
+        std::make_pair("ArgumentStorageImage", "%int_0 %int_0"),
+        std::make_pair("ArgumentSampler", "%int_0 %int_0"),
+        std::make_pair("ArgumentWorkgroup", "%int_0 %int_0")}));
+
+TEST_P(ArgumentBasics, KernelNotAnExtendedInstruction) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string extra = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%in = OpExtInst %void %ext )" +
+                           ext_inst + " %int_0 %int_0 " + extra;
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Kernel must be a Kernel extended instruction"));
+}
+
+TEST_P(ArgumentBasics, KernelFromDifferentImport) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string extra = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+%ext2 = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext2 Kernel %foo %foo_name
+%in = OpExtInst %void %ext )" +
+                           ext_inst + " %decl %int_0 " + extra;
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("Kernel must be from the same extended instruction import"));
+}
+
+TEST_P(ArgumentBasics, KernelWrongExtendedInstruction) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string extra = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext ArgumentInfo %foo_name
+%in = OpExtInst %void %ext )" +
+                           ext_inst + " %decl %int_0 " + extra;
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Kernel must be a Kernel extended instruction"));
+}
+
+TEST_P(ArgumentBasics, ArgumentInfo) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string operands = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%in_name = OpString "in"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+%info = OpExtInst %void %ext ArgumentInfo %in_name
+%in = OpExtInst %void %ext )" +
+                           ext_inst + " %decl %int_0 " + operands + " %info";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_P(ArgumentBasics, ArgumentInfoNotAnExtendedInstruction) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string operands = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+%in = OpExtInst %void %ext )" +
+                           ext_inst + " %decl %int_0 " + operands + " %int_0";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ArgInfo must be an ArgumentInfo extended instruction"));
+}
+
+TEST_P(ArgumentBasics, ArgumentInfoFromDifferentImport) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string operands = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+%ext2 = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%in_name = OpString "in"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+%info = OpExtInst %void %ext2 ArgumentInfo %in_name
+%in = OpExtInst %void %ext )" +
+                           ext_inst + " %decl %int_0 " + operands + " %info";
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr("ArgInfo must be from the same extended instruction import"));
+}
+
+using Uint32Constant =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateClspvReflectionUint32Constants, Uint32Constant,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair("ArgumentStorageBuffer %decl %float_0 %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentStorageBuffer %decl %null %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentStorageBuffer %decl %int_0 %float_0 %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentStorageBuffer %decl %int_0 %null %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentStorageBuffer %decl %int_0 %int_0 %float_0",
+                       "Binding"),
+        std::make_pair("ArgumentStorageBuffer %decl %int_0 %int_0 %null",
+                       "Binding"),
+        std::make_pair("ArgumentUniform %decl %float_0 %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentUniform %decl %null %int_0 %int_0", "Ordinal"),
+        std::make_pair("ArgumentUniform %decl %int_0 %float_0 %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentUniform %decl %int_0 %null %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentUniform %decl %int_0 %int_0 %float_0",
+                       "Binding"),
+        std::make_pair("ArgumentUniform %decl %int_0 %int_0 %null", "Binding"),
+        std::make_pair("ArgumentSampledImage %decl %float_0 %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentSampledImage %decl %null %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentSampledImage %decl %int_0 %float_0 %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentSampledImage %decl %int_0 %null %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentSampledImage %decl %int_0 %int_0 %float_0",
+                       "Binding"),
+        std::make_pair("ArgumentSampledImage %decl %int_0 %int_0 %null",
+                       "Binding"),
+        std::make_pair("ArgumentStorageImage %decl %float_0 %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentStorageImage %decl %null %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentStorageImage %decl %int_0 %float_0 %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentStorageImage %decl %int_0 %null %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentStorageImage %decl %int_0 %int_0 %float_0",
+                       "Binding"),
+        std::make_pair("ArgumentStorageImage %decl %int_0 %int_0 %null",
+                       "Binding"),
+        std::make_pair("ArgumentSampler %decl %float_0 %int_0 %int_0",
+                       "Ordinal"),
+        std::make_pair("ArgumentSampler %decl %null %int_0 %int_0", "Ordinal"),
+        std::make_pair("ArgumentSampler %decl %int_0 %float_0 %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentSampler %decl %int_0 %null %int_0",
+                       "DescriptorSet"),
+        std::make_pair("ArgumentSampler %decl %int_0 %int_0 %float_0",
+                       "Binding"),
+        std::make_pair("ArgumentSampler %decl %int_0 %int_0 %null", "Binding"),
+        std::make_pair("ArgumentPodStorageBuffer %decl %float_0 %int_0 %int_0 "
+                       "%int_0 %int_4",
+                       "Ordinal"),
+        std::make_pair(
+            "ArgumentPodStorageBuffer %decl %null %int_0 %int_0 %int_0 %int_4",
+            "Ordinal"),
+        std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %float_0 %int_0 "
+                       "%int_0 %int_4",
+                       "DescriptorSet"),
+        std::make_pair(
+            "ArgumentPodStorageBuffer %decl %int_0 %null %int_0 %int_0 %int_4",
+            "DescriptorSet"),
+        std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %int_0 %float_0 "
+                       "%int_0 %int_4",
+                       "Binding"),
+        std::make_pair(
+            "ArgumentPodStorageBuffer %decl %int_0 %int_0 %null %int_0 %int_4",
+            "Binding"),
+        std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 "
+                       "%float_0 %int_4",
+                       "Offset"),
+        std::make_pair(
+            "ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 %null %int_4",
+            "Offset"),
+        std::make_pair("ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 "
+                       "%int_0 %float_0",
+                       "Size"),
+        std::make_pair(
+            "ArgumentPodStorageBuffer %decl %int_0 %int_0 %int_0 %int_0 %null",
+            "Size"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %float_0 %int_0 %int_0 %int_0 %int_4",
+            "Ordinal"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %null %int_0 %int_0 %int_0 %int_4",
+            "Ordinal"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %float_0 %int_0 %int_0 %int_4",
+            "DescriptorSet"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %null %int_0 %int_0 %int_4",
+            "DescriptorSet"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %int_0 %float_0 %int_0 %int_4",
+            "Binding"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %int_0 %null %int_0 %int_4",
+            "Binding"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %float_0 %int_4",
+            "Offset"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %null %int_4",
+            "Offset"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %int_0 %float_0",
+            "Size"),
+        std::make_pair(
+            "ArgumentPodUniform %decl %int_0 %int_0 %int_0 %int_0 %null",
+            "Size"),
+        std::make_pair("ArgumentPodPushConstant %decl %float_0 %int_0 %int_4",
+                       "Ordinal"),
+        std::make_pair("ArgumentPodPushConstant %decl %null %int_0 %int_4",
+                       "Ordinal"),
+        std::make_pair("ArgumentPodPushConstant %decl %int_0 %float_0 %int_4",
+                       "Offset"),
+        std::make_pair("ArgumentPodPushConstant %decl %int_0 %null %int_4",
+                       "Offset"),
+        std::make_pair("ArgumentPodPushConstant %decl %int_0 %int_0 %float_0",
+                       "Size"),
+        std::make_pair("ArgumentPodPushConstant %decl %int_0 %int_0 %null",
+                       "Size"),
+        std::make_pair("ArgumentWorkgroup %decl %float_0 %int_0 %int_4",
+                       "Ordinal"),
+        std::make_pair("ArgumentWorkgroup %decl %null %int_0 %int_4",
+                       "Ordinal"),
+        std::make_pair("ArgumentWorkgroup %decl %int_0 %float_0 %int_4",
+                       "SpecId"),
+        std::make_pair("ArgumentWorkgroup %decl %int_0 %null %int_4", "SpecId"),
+        std::make_pair("ArgumentWorkgroup %decl %int_0 %int_0 %float_0",
+                       "ElemSize"),
+        std::make_pair("ArgumentWorkgroup %decl %int_0 %int_0 %null",
+                       "ElemSize"),
+        std::make_pair("SpecConstantWorkgroupSize %float_0 %int_0 %int_4", "X"),
+        std::make_pair("SpecConstantWorkgroupSize %null %int_0 %int_4", "X"),
+        std::make_pair("SpecConstantWorkgroupSize %int_0 %float_0 %int_4", "Y"),
+        std::make_pair("SpecConstantWorkgroupSize %int_0 %null %int_4", "Y"),
+        std::make_pair("SpecConstantWorkgroupSize %int_0 %int_0 %float_0", "Z"),
+        std::make_pair("SpecConstantWorkgroupSize %int_0 %int_0 %null", "Z"),
+        std::make_pair("SpecConstantGlobalOffset %float_0 %int_0 %int_4", "X"),
+        std::make_pair("SpecConstantGlobalOffset %null %int_0 %int_4", "X"),
+        std::make_pair("SpecConstantGlobalOffset %int_0 %float_0 %int_4", "Y"),
+        std::make_pair("SpecConstantGlobalOffset %int_0 %null %int_4", "Y"),
+        std::make_pair("SpecConstantGlobalOffset %int_0 %int_0 %float_0", "Z"),
+        std::make_pair("SpecConstantGlobalOffset %int_0 %int_0 %null", "Z"),
+        std::make_pair("SpecConstantWorkDim %float_0", "Dim"),
+        std::make_pair("SpecConstantWorkDim %null", "Dim"),
+        std::make_pair("PushConstantGlobalOffset %float_0 %int_0", "Offset"),
+        std::make_pair("PushConstantGlobalOffset %null %int_0", "Offset"),
+        std::make_pair("PushConstantGlobalOffset %int_0 %float_0", "Size"),
+        std::make_pair("PushConstantGlobalOffset %int_0 %null", "Size"),
+        std::make_pair("PushConstantEnqueuedLocalSize %float_0 %int_0",
+                       "Offset"),
+        std::make_pair("PushConstantEnqueuedLocalSize %null %int_0", "Offset"),
+        std::make_pair("PushConstantEnqueuedLocalSize %int_0 %float_0", "Size"),
+        std::make_pair("PushConstantEnqueuedLocalSize %int_0 %null", "Size"),
+        std::make_pair("PushConstantGlobalSize %float_0 %int_0", "Offset"),
+        std::make_pair("PushConstantGlobalSize %null %int_0", "Offset"),
+        std::make_pair("PushConstantGlobalSize %int_0 %float_0", "Size"),
+        std::make_pair("PushConstantGlobalSize %int_0 %null", "Size"),
+        std::make_pair("PushConstantRegionOffset %float_0 %int_0", "Offset"),
+        std::make_pair("PushConstantRegionOffset %null %int_0", "Offset"),
+        std::make_pair("PushConstantRegionOffset %int_0 %float_0", "Size"),
+        std::make_pair("PushConstantRegionOffset %int_0 %null", "Size"),
+        std::make_pair("PushConstantNumWorkgroups %float_0 %int_0", "Offset"),
+        std::make_pair("PushConstantNumWorkgroups %null %int_0", "Offset"),
+        std::make_pair("PushConstantNumWorkgroups %int_0 %float_0", "Size"),
+        std::make_pair("PushConstantNumWorkgroups %int_0 %null", "Size"),
+        std::make_pair("PushConstantRegionGroupOffset %float_0 %int_0",
+                       "Offset"),
+        std::make_pair("PushConstantRegionGroupOffset %null %int_0", "Offset"),
+        std::make_pair("PushConstantRegionGroupOffset %int_0 %float_0", "Size"),
+        std::make_pair("PushConstantRegionGroupOffset %int_0 %null", "Size"),
+        std::make_pair("ConstantDataStorageBuffer %float_0 %int_0 %data",
+                       "DescriptorSet"),
+        std::make_pair("ConstantDataStorageBuffer %null %int_0 %data",
+                       "DescriptorSet"),
+        std::make_pair("ConstantDataStorageBuffer %int_0 %float_0 %data",
+                       "Binding"),
+        std::make_pair("ConstantDataStorageBuffer %int_0 %null %data",
+                       "Binding"),
+        std::make_pair("ConstantDataUniform %float_0 %int_0 %data",
+                       "DescriptorSet"),
+        std::make_pair("ConstantDataUniform %null %int_0 %data",
+                       "DescriptorSet"),
+        std::make_pair("ConstantDataUniform %int_0 %float_0 %data", "Binding"),
+        std::make_pair("ConstantDataUniform %int_0 %null %data", "Binding"),
+        std::make_pair("LiteralSampler %float_0 %int_0 %int_4",
+                       "DescriptorSet"),
+        std::make_pair("LiteralSampler %null %int_0 %int_4", "DescriptorSet"),
+        std::make_pair("LiteralSampler %int_0 %float_0 %int_4", "Binding"),
+        std::make_pair("LiteralSampler %int_0 %null %int_4", "Binding"),
+        std::make_pair("LiteralSampler %int_0 %int_0 %float_0", "Mask"),
+        std::make_pair("LiteralSampler %int_0 %int_0 %null", "Mask"),
+        std::make_pair(
+            "PropertyRequiredWorkgroupSize %decl %float_0 %int_1 %int_4", "X"),
+        std::make_pair(
+            "PropertyRequiredWorkgroupSize %decl %null %int_1 %int_4", "X"),
+        std::make_pair(
+            "PropertyRequiredWorkgroupSize %decl %int_1 %float_0 %int_4", "Y"),
+        std::make_pair(
+            "PropertyRequiredWorkgroupSize %decl %int_1 %null %int_4", "Y"),
+        std::make_pair(
+            "PropertyRequiredWorkgroupSize %decl %int_1 %int_1 %float_0", "Z"),
+        std::make_pair(
+            "PropertyRequiredWorkgroupSize %decl %int_1 %int_1 %null", "Z")}));
+
+TEST_P(Uint32Constant, Invalid) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string name = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%data = OpString "1234"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_4 = OpConstant %int 4
+%null = OpConstantNull %int
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+%inst = OpExtInst %void %ext )" +
+                           ext_inst;
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(
+      getDiagnosticString(),
+      HasSubstr(name + " must be a 32-bit unsigned integer OpConstant"));
+}
+
+using StringOperand =
+    spvtest::ValidateBase<std::pair<std::string, std::string>>;
+
+INSTANTIATE_TEST_SUITE_P(
+    ValidateClspvReflectionStringOperands, StringOperand,
+    ::testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
+        std::make_pair("ConstantDataStorageBuffer %int_0 %int_0 %int_0",
+                       "Data"),
+        std::make_pair("ConstantDataUniform %int_0 %int_0 %int_0", "Data")}));
+
+TEST_P(StringOperand, Invalid) {
+  const std::string ext_inst = std::get<0>(GetParam());
+  const std::string name = std::get<1>(GetParam());
+  const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_non_semantic_info"
+%ext = OpExtInstImport "NonSemantic.ClspvReflection.1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %foo "foo"
+OpExecutionMode %foo LocalSize 1 1 1
+%foo_name = OpString "foo"
+%data = OpString "1234"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_4 = OpConstant %int 4
+%null = OpConstantNull %int
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+%decl = OpExtInst %void %ext Kernel %foo %foo_name
+%inst = OpExtInst %void %ext )" +
+                           ext_inst;
+
+  CompileSuccessfully(text);
+  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(), HasSubstr(name + " must be an OpString"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp
index 1a6e79c..2e84a29 100644
--- a/test/val/val_image_test.cpp
+++ b/test/val/val_image_test.cpp
@@ -1,4 +1,6 @@
 // Copyright (c) 2017 Google Inc.
+// Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights
+// reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -37,7 +39,8 @@
     const std::string& execution_model = "Fragment",
     const std::string& execution_mode = "",
     const spv_target_env env = SPV_ENV_UNIVERSAL_1_0,
-    const std::string& memory_model = "GLSL450") {
+    const std::string& memory_model = "GLSL450",
+    const std::string& declarations = "") {
   std::ostringstream ss;
   ss << R"(
 OpCapability Shader
@@ -163,6 +166,7 @@
 %u32_4 = OpConstant %u32 4
 
 %u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
 
 %u32vec2arr4 = OpTypeArray %u32vec2 %u32_4
 %u32vec2arr3 = OpTypeArray %u32vec2 %u32_3
@@ -316,6 +320,8 @@
 )";
   }
 
+  ss << declarations;
+
   ss << R"(
 %main = OpFunction %void None %func
 %main_entry = OpLabel
@@ -737,7 +743,7 @@
 
   CompileSuccessfully(GenerateShaderCode(body).c_str());
   ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
-  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 140[%140] cannot be a "
+  EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 141[%141] cannot be a "
                                                "type"));
 }
 
@@ -5071,6 +5077,102 @@
 // No negative tests for ZeroExtend since we don't truly know the
 // texel format.
 
+// Tests for 64-bit images
+static const std::string capabilities_and_extensions_image64 = R"(
+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
+%ptr_image_u64_buffer_0002_r64ui = OpTypePointer Private %type_image_u64_buffer_0002_r64ui
+%private_image_u64_buffer_0002_r64ui = OpVariable %ptr_image_u64_buffer_0002_r64ui Private
+)";
+
+TEST_F(ValidateImage, Image64MissingCapability) {
+  CompileSuccessfully(GenerateShaderCode("", "", "Fragment", "",
+                                         SPV_ENV_UNIVERSAL_1_3, "GLSL450",
+                                         declarations_image64)
+                          .c_str());
+  ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, Image64MissingExtension) {
+  const std::string extra = R"(
+OpCapability Int64ImageEXT
+)";
+
+  CompileSuccessfully(GenerateShaderCode("", extra, "Fragment", "",
+                                         SPV_ENV_UNIVERSAL_1_3, "GLSL450",
+                                         declarations_image64)
+                          .c_str());
+  ASSERT_EQ(SPV_ERROR_MISSING_EXTENSION, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, ImageTexelPointer64Success) {
+  const std::string body = R"(
+%texel_ptr = OpImageTexelPointer %ptr_Image_u64 %private_image_u64_buffer_0002_r64ui %u32_0 %u32_0
+%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());
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateImage, ImageTexelPointer64ResultTypeNotPointer) {
+  const std::string body = R"(
+%texel_ptr = OpImageTexelPointer %type_image_u64_buffer_0002_r64ui %private_image_u64_buffer_0002_r64ui %u32_0 %u32_0
+%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());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type to be OpTypePointer"));
+}
+
+TEST_F(ValidateImage, ImageTexelPointer64ResultTypeNotImageClass) {
+  const std::string body = R"(
+%texel_ptr = OpImageTexelPointer %ptr_image_f32_cube_0101 %private_image_u64_buffer_0002_r64ui %u32_0 %u32_0
+%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());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Result Type to be OpTypePointer whose "
+                        "Storage Class operand is Image"));
+}
+
+TEST_F(ValidateImage, ImageTexelPointer64SampleNotZeroForImageWithMSZero) {
+  const std::string body = R"(
+%texel_ptr = OpImageTexelPointer %ptr_Image_u64 %private_image_u64_buffer_0002_r64ui %u32_0 %u32_1
+%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());
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("Expected Sample for Image with MS 0 to be a valid "
+                        "<id> for the value 0"));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp
index f2fb45a..6869e79 100644
--- a/test/val/val_interfaces_test.cpp
+++ b/test/val/val_interfaces_test.cpp
@@ -1220,9 +1220,11 @@
 TEST_F(ValidateInterfacesTest, VulkanLocationsIndexGLCompute) {
   const std::string text = R"(
 OpCapability Shader
+OpCapability Geometry
 OpMemoryModel Logical GLSL450
-OpEntryPoint GLCompute %main "main" %var1
-OpExecutionMode %main LocalSize 1 1 1
+OpEntryPoint Geometry %main "main" %var1
+OpExecutionMode %main Triangles
+OpExecutionMode %main OutputPoints
 OpDecorate %var1 Location 1
 OpDecorate %var1 Index 1
 %void = OpTypeVoid
@@ -1379,6 +1381,35 @@
   EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateInterfacesTest, VulkanLocationMeshShader) {
+  const std::string text = R"(
+OpCapability Shader
+OpCapability MeshShadingNV
+OpExtension "SPV_NV_mesh_shader"
+OpMemoryModel Logical GLSL450
+OpEntryPoint MeshNV %foo "foo" %in
+OpExecutionMode %foo LocalSize 1 1 1
+OpDecorate %block Block
+OpMemberDecorate %block 0 PerTaskNV
+OpMemberDecorate %block 0 Offset 0
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int_32 = OpConstant %int 32
+%array = OpTypeArray %int %int_32
+%block = OpTypeStruct %array
+%ptr_input_block = OpTypePointer Input %block
+%in = OpVariable %ptr_input_block Input
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  CompileSuccessfully(text, SPV_ENV_VULKAN_1_2);
+  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index 9d4491c..43fa046 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -567,6 +567,26 @@
   EXPECT_THAT(getDiagnosticString(), Eq(""));
 }
 
+TEST_F(ValidateLayout, LayoutOrderMixedUp) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability Linkage
+           OpMemoryModel Logical GLSL450
+           OpEntryPoint Fragment %fragmentFloat "fragmentFloat"
+           OpExecutionMode %fragmentFloat OriginUpperLeft
+           OpEntryPoint Fragment %fragmentUint "fragmentUint"
+           OpExecutionMode %fragmentUint OriginUpperLeft
+)";
+
+  CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
+            ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
+  // By the mechanics of the validator, we assume ModuleProcessed is in the
+  // right spot, but then that OpName is in the wrong spot.
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("EntryPoint is in an invalid layout section"));
+}
+
 TEST_F(ValidateLayout, ModuleProcessedBeforeLastNameIsTooEarly) {
   char str[] = R"(
            OpCapability Shader
@@ -583,7 +603,7 @@
   // By the mechanics of the validator, we assume ModuleProcessed is in the
   // right spot, but then that OpName is in the wrong spot.
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("Name cannot appear in a function declaration"));
+              HasSubstr("Name is in an invalid layout section"));
 }
 
 TEST_F(ValidateLayout, ModuleProcessedInvalidAfterFirstAnnotation) {
@@ -599,9 +619,8 @@
   CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
   ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
             ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("ModuleProcessed cannot appear in a function declaration"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ModuleProcessed is in an invalid layout section"));
 }
 
 TEST_F(ValidateLayout, ModuleProcessedInvalidInFunctionBeforeLabel) {
diff --git a/test/val/val_primitives_test.cpp b/test/val/val_primitives_test.cpp
index 04d0a4f..8a61723 100644
--- a/test/val/val_primitives_test.cpp
+++ b/test/val/val_primitives_test.cpp
@@ -77,7 +77,7 @@
 std::string CallAndCallee(const std::string& body) {
   std::ostringstream ss;
   ss << R"(
-%dummy = OpFunctionCall %void %foo
+%placeholder = OpFunctionCall %void %foo
 OpReturn
 OpFunctionEnd
 
diff --git a/test/val/val_state_test.cpp b/test/val/val_state_test.cpp
index 18a4ef9..b2d2604 100644
--- a/test/val/val_state_test.cpp
+++ b/test/val/val_state_test.cpp
@@ -134,6 +134,57 @@
   EXPECT_FALSE(state_.HasAnyOfExtensions(set2));
 }
 
+// A test of ValidationState_t::IsOpcodeInCurrentLayoutSection().
+using ValidationState_InLayoutState = ValidationStateTest;
+
+TEST_F(ValidationState_InLayoutState, Variable) {
+  state_.SetCurrentLayoutSectionForTesting(kLayoutTypes);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpVariable));
+
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDefinitions);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpVariable));
+}
+
+TEST_F(ValidationState_InLayoutState, ExtInst) {
+  state_.SetCurrentLayoutSectionForTesting(kLayoutTypes);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpExtInst));
+
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDefinitions);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpExtInst));
+}
+
+TEST_F(ValidationState_InLayoutState, Undef) {
+  state_.SetCurrentLayoutSectionForTesting(kLayoutTypes);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpUndef));
+
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDefinitions);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpUndef));
+}
+
+TEST_F(ValidationState_InLayoutState, Function) {
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDeclarations);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpFunction));
+
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDefinitions);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpFunction));
+}
+
+TEST_F(ValidationState_InLayoutState, FunctionParameter) {
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDeclarations);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpFunctionParameter));
+
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDefinitions);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpFunctionParameter));
+}
+
+TEST_F(ValidationState_InLayoutState, FunctionEnd) {
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDeclarations);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpFunctionEnd));
+
+  state_.SetCurrentLayoutSectionForTesting(kLayoutFunctionDefinitions);
+  EXPECT_TRUE(state_.IsOpcodeInCurrentLayoutSection(SpvOpFunctionEnd));
+}
+
 }  // namespace
 }  // namespace val
 }  // namespace spvtools
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index b3a4cc1..124a332 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -40,19 +40,19 @@
 endfunction()
 
 if (NOT ${SPIRV_SKIP_EXECUTABLES})
-  add_spvtools_tool(TARGET spirv-as SRCS as/as.cpp LIBS ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS})
-  add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
+  add_spvtools_tool(TARGET spirv-as SRCS as/as.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
+  add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY})
   if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires
-    add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
+    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})
+  add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS_FULL_VISIBILITY})
   add_spvtools_tool(TARGET spirv-cfg
                     SRCS cfg/cfg.cpp
                          cfg/bin_to_dot.h
                          cfg/bin_to_dot.cpp
-                    LIBS ${SPIRV_TOOLS})
+                    LIBS ${SPIRV_TOOLS_FULL_VISIBILITY})
   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
@@ -62,7 +62,7 @@
   endif()
 
   if(SPIRV_BUILD_FUZZER)
-    add_spvtools_tool(TARGET spirv-fuzz SRCS fuzz/fuzz.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS})
+    add_spvtools_tool(TARGET spirv-fuzz SRCS fuzz/fuzz.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS_FULL_VISIBILITY})
     set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-fuzz)
   endif(SPIRV_BUILD_FUZZER)
 
diff --git a/tools/dis/dis.cpp b/tools/dis/dis.cpp
index c0ce267..bdeeef1 100644
--- a/tools/dis/dis.cpp
+++ b/tools/dis/dis.cpp
@@ -56,6 +56,8 @@
   --raw-id        Show raw Id values instead of friendly names.
 
   --offsets       Show byte offsets for each instruction.
+
+  --comment       Add comments to make reading easier
 )",
       argv0, argv0);
 }
@@ -79,6 +81,7 @@
   bool show_byte_offsets = false;
   bool no_header = false;
   bool friendly_names = true;
+  bool comments = false;
 
   for (int argi = 1; argi < argc; ++argi) {
     if ('-' == argv[argi][0]) {
@@ -102,6 +105,8 @@
           } else if (0 == strcmp(argv[argi], "--color")) {
             force_no_color = false;
             force_color = true;
+          } else if (0 == strcmp(argv[argi], "--comment")) {
+            comments = true;
           } else if (0 == strcmp(argv[argi], "--no-indent")) {
             allow_indent = false;
           } else if (0 == strcmp(argv[argi], "--offsets")) {
@@ -156,6 +161,8 @@
 
   if (friendly_names) options |= SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
 
+  if (comments) options |= SPV_BINARY_TO_TEXT_OPTION_COMMENT;
+
   if (!outFile || (0 == strcmp("-", outFile))) {
     // Print to standard output.
     options |= SPV_BINARY_TO_TEXT_OPTION_PRINT;
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 61064d1..5ee0fb1 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -16,7 +16,7 @@
 #include <cerrno>
 #include <cstring>
 #include <fstream>
-#include <functional>
+#include <memory>
 #include <random>
 #include <sstream>
 #include <string>
@@ -25,12 +25,14 @@
 #include "source/fuzz/fuzzer.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/pseudo_random_generator.h"
 #include "source/fuzz/replayer.h"
 #include "source/fuzz/shrinker.h"
 #include "source/opt/build_module.h"
 #include "source/opt/ir_context.h"
 #include "source/opt/log.h"
 #include "source/spirv_fuzzer_options.h"
+#include "source/util/make_unique.h"
 #include "source/util/string_utils.h"
 #include "tools/io.h"
 #include "tools/util/cli_consumer.h"
@@ -107,6 +109,14 @@
                provided if the tool is invoked in fuzzing mode; incompatible
                with replay and shrink modes.  The file should be empty if no
                donors are to be used.
+  --enable-all-passes
+               By default, spirv-fuzz follows the philosophy of "swarm testing"
+               (Groce et al., 2012): only a subset of fuzzer passes are enabled
+               on any given fuzzer run, with the subset being chosen randomly.
+               This flag instead forces *all* fuzzer passes to be enabled.  When
+               running spirv-fuzz many times this is likely to produce *less*
+               diverse fuzzed modules than when swarm testing is used.  The
+               purpose of the flag is to allow that hypothesis to be tested.
   --force-render-red
                Transforms the input shader into a shader that writes red to the
                output buffer, and then captures the original shader as the body
@@ -118,6 +128,19 @@
                Run the validator after applying each fuzzer pass during
                fuzzing.  Aborts fuzzing early if an invalid binary is created.
                Useful for debugging spirv-fuzz.
+  --repeated-pass-strategy=
+               Available strategies are:
+               - looped (the default): a sequence of fuzzer passes is chosen at
+                 the start of fuzzing, via randomly choosing enabled passes, and
+                 augmenting these choices with fuzzer passes that it is
+                 recommended to run subsequently.  Fuzzing then involves
+                 repeatedly applying this fixed sequence of passes.
+               - random: each time a fuzzer pass is requested, this strategy
+                 either provides one at random from the set of enabled passes,
+                 or provides a pass that has been recommended based on a pass
+                 that was used previously.
+               - simple: each time a fuzzer pass is requested, one is provided
+                 at random from the set of enabled passes.
   --replay
                File from which to read a sequence of transformations to replay
                (instead of fuzzing)
@@ -174,18 +197,23 @@
   fprintf(stderr, "%s\n", message);
 }
 
-FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
-                      std::string* out_binary_file, std::string* donors_file,
-                      std::string* replay_transformations_file,
-                      std::vector<std::string>* interestingness_test,
-                      std::string* shrink_transformations_file,
-                      std::string* shrink_temp_file_prefix,
-                      spvtools::FuzzerOptions* fuzzer_options,
-                      spvtools::ValidatorOptions* validator_options) {
+FuzzStatus ParseFlags(
+    int argc, const char** argv, std::string* in_binary_file,
+    std::string* out_binary_file, std::string* donors_file,
+    std::string* replay_transformations_file,
+    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::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;
+
   for (int argi = 1; argi < argc; ++argi) {
     const char* cur_arg = argv[argi];
     if ('-' == cur_arg[0] && !only_positional_arguments_remain) {
@@ -206,6 +234,9 @@
       } else if (0 == strncmp(cur_arg, "--donors=", sizeof("--donors=") - 1)) {
         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
         *donors_file = std::string(split_flag.second);
+      } else if (0 == strncmp(cur_arg, "--enable-all-passes",
+                              sizeof("--enable-all-passes") - 1)) {
+        fuzzer_options->enable_all_passes();
       } else if (0 == strncmp(cur_arg, "--force-render-red",
                               sizeof("--force-render-red") - 1)) {
         force_render_red = true;
@@ -215,6 +246,26 @@
       } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
         *replay_transformations_file = std::string(split_flag.second);
+      } else if (0 == strncmp(cur_arg, "--repeated-pass-strategy=",
+                              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;
+        } else if (strategy == "random") {
+          *repeated_pass_strategy = spvtools::fuzz::Fuzzer::
+              RepeatedPassStrategy::kRandomWithRecommendations;
+        } else if (strategy == "simple") {
+          *repeated_pass_strategy =
+              spvtools::fuzz::Fuzzer::RepeatedPassStrategy::kSimple;
+        } else {
+          std::stringstream ss;
+          ss << "Unknown repeated pass strategy '" << strategy << "'"
+             << std::endl;
+          ss << "Valid options are 'looped', 'random' and 'simple'.";
+          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);
@@ -405,9 +456,6 @@
                             &transformation_sequence)) {
     return false;
   }
-  spvtools::fuzz::Replayer replayer(
-      target_env, fuzzer_options->replay_validation_enabled, validator_options);
-  replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
 
   uint32_t num_transformations_to_apply;
   if (fuzzer_options->replay_range > 0) {
@@ -426,11 +474,16 @@
                         fuzzer_options->replay_range));
   }
 
-  auto replay_result_status = replayer.Run(
-      binary_in, initial_facts, transformation_sequence,
-      num_transformations_to_apply, binary_out, transformations_applied);
-  return !(replay_result_status !=
-           spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
+  auto replay_result =
+      spvtools::fuzz::Replayer(
+          target_env, spvtools::utils::CLIMessageConsumer, binary_in,
+          initial_facts, transformation_sequence, num_transformations_to_apply,
+          fuzzer_options->replay_validation_enabled, validator_options)
+          .Run();
+  replay_result.transformed_module->module()->ToBinary(binary_out, false);
+  *transformations_applied = std::move(replay_result.applied_transformations);
+  return replay_result.status ==
+         spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete;
 }
 
 bool Shrink(const spv_target_env& target_env,
@@ -449,11 +502,6 @@
                             &transformation_sequence)) {
     return false;
   }
-  spvtools::fuzz::Shrinker shrinker(
-      target_env, fuzzer_options->shrinker_step_limit,
-      fuzzer_options->replay_validation_enabled, validator_options);
-  shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
-
   assert(!interestingness_command.empty() &&
          "An error should have been raised because the interestingness_command "
          "is empty.");
@@ -479,13 +527,20 @@
     return ExecuteCommand(command);
   };
 
-  auto shrink_result_status = shrinker.Run(
-      binary_in, initial_facts, transformation_sequence,
-      interestingness_function, binary_out, transformations_applied);
+  auto shrink_result =
+      spvtools::fuzz::Shrinker(
+          target_env, spvtools::utils::CLIMessageConsumer, binary_in,
+          initial_facts, transformation_sequence, interestingness_function,
+          fuzzer_options->shrinker_step_limit,
+          fuzzer_options->replay_validation_enabled, validator_options)
+          .Run();
+
+  *binary_out = std::move(shrink_result.transformed_binary);
+  *transformations_applied = std::move(shrink_result.applied_transformations);
   return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
-             shrink_result_status ||
+             shrink_result.status ||
          spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
-             shrink_result_status;
+             shrink_result.status;
 }
 
 bool Fuzz(const spv_target_env& target_env,
@@ -493,7 +548,9 @@
           spv_validator_options validator_options,
           const std::vector<uint32_t>& binary_in,
           const spvtools::fuzz::protobufs::FactSequence& initial_facts,
-          const std::string& donors, std::vector<uint32_t>* binary_out,
+          const std::string& donors,
+          spvtools::fuzz::Fuzzer::RepeatedPassStrategy repeated_pass_strategy,
+          std::vector<uint32_t>* binary_out,
           spvtools::fuzz::protobufs::TransformationSequence*
               transformations_applied) {
   auto message_consumer = spvtools::utils::CLIMessageConsumer;
@@ -521,17 +578,20 @@
         });
   }
 
-  spvtools::fuzz::Fuzzer fuzzer(
-      target_env,
-      fuzzer_options->has_random_seed
-          ? fuzzer_options->random_seed
-          : static_cast<uint32_t>(std::random_device()()),
-      fuzzer_options->fuzzer_pass_validation_enabled, validator_options);
-  fuzzer.SetMessageConsumer(message_consumer);
-  auto fuzz_result_status =
-      fuzzer.Run(binary_in, initial_facts, donor_suppliers, binary_out,
-                 transformations_applied);
-  if (fuzz_result_status !=
+  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) {
     spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
     return false;
@@ -558,6 +618,34 @@
   DumpShader(binary, filename);
 }
 
+// Dumps |transformations| to file |filename| in binary format. Useful for
+// interactive debugging.
+void DumpTransformationsBinary(
+    const spvtools::fuzz::protobufs::TransformationSequence& transformations,
+    const char* filename) {
+  std::ofstream transformations_file;
+  transformations_file.open(filename, std::ios::out | std::ios::binary);
+  transformations.SerializeToOstream(&transformations_file);
+  transformations_file.close();
+}
+
+// Dumps |transformations| to file |filename| in JSON format. Useful for
+// interactive debugging.
+void DumpTransformationsJson(
+    const spvtools::fuzz::protobufs::TransformationSequence& transformations,
+    const char* filename) {
+  std::string json_string;
+  auto json_options = google::protobuf::util::JsonOptions();
+  json_options.add_whitespace = true;
+  auto json_generation_status = google::protobuf::util::MessageToJsonString(
+      transformations, &json_string, json_options);
+  if (json_generation_status == google::protobuf::util::Status::OK) {
+    std::ofstream transformations_json_file(filename);
+    transformations_json_file << json_string;
+    transformations_json_file.close();
+  }
+}
+
 const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
 
 int main(int argc, const char** argv) {
@@ -568,6 +656,7 @@
   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::FuzzerOptions fuzzer_options;
   spvtools::ValidatorOptions validator_options;
@@ -576,7 +665,7 @@
       ParseFlags(argc, argv, &in_binary_file, &out_binary_file, &donors_file,
                  &replay_transformations_file, &interestingness_test,
                  &shrink_transformations_file, &shrink_temp_file_prefix,
-                 &fuzzer_options, &validator_options);
+                 &repeated_pass_strategy, &fuzzer_options, &validator_options);
 
   if (status.action == FuzzActions::STOP) {
     return status.code;
@@ -622,7 +711,7 @@
       break;
     case FuzzActions::FUZZ:
       if (!Fuzz(target_env, fuzzer_options, validator_options, binary_in,
-                initial_facts, donors_file, &binary_out,
+                initial_facts, donors_file, repeated_pass_strategy, &binary_out,
                 &transformations_applied)) {
         return 1;
       }
diff --git a/tools/io.h b/tools/io.h
index 97a3163..f9cfd9d 100644
--- a/tools/io.h
+++ b/tools/io.h
@@ -17,6 +17,7 @@
 
 #include <cstdint>
 #include <cstdio>
+#include <cstring>
 #include <vector>
 
 // Appends the content from the file named as |filename| to |data|, assuming
diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp
index 0bdeb82..49a5efe 100644
--- a/tools/reduce/reduce.cpp
+++ b/tools/reduce/reduce.cpp
@@ -16,6 +16,7 @@
 #include <cerrno>
 #include <cstring>
 #include <functional>
+#include <sstream>
 
 #include "source/opt/build_module.h"
 #include "source/opt/ir_context.h"
@@ -102,6 +103,14 @@
   --step-limit=
                32-bit unsigned integer specifying maximum number of steps the
                reducer will take before giving up.
+  --target-function=
+               32-bit unsigned integer specifying the id of a function in the
+               input module.  The reducer will restrict attention to this
+               function, and will not make changes to other functions or to
+               instructions outside of functions, except that some global
+               instructions may be added in support of reducing the target
+               function.  If 0 is specified (the default) then all functions are
+               reduced.
   --temp-file-prefix=
                Specifies a temporary file prefix that will be used to output
                temporary shader files during reduction.  A number and .spv
@@ -169,6 +178,15 @@
             static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
         assert(end != split_flag.second.c_str() && errno == 0);
         reducer_options->set_step_limit(step_limit);
+      } else if (0 == strncmp(cur_arg, "--target-function=",
+                              sizeof("--target-function=") - 1)) {
+        const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
+        char* end = nullptr;
+        errno = 0;
+        const auto target_function =
+            static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
+        assert(end != split_flag.second.c_str() && errno == 0);
+        reducer_options->set_target_function(target_function);
       } else if (0 == strcmp(cur_arg, "--fail-on-validation-error")) {
         reducer_options->set_fail_on_validation_error(true);
       } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
@@ -304,6 +322,29 @@
     return 1;
   }
 
+  const uint32_t target_function = (*reducer_options).target_function;
+  if (target_function) {
+    // A target function was specified; check that it exists.
+    std::unique_ptr<spvtools::opt::IRContext> context = spvtools::BuildModule(
+        kDefaultEnvironment, spvtools::utils::CLIMessageConsumer,
+        binary_in.data(), binary_in.size());
+    bool found_target_function = false;
+    for (auto& function : *context->module()) {
+      if (function.result_id() == target_function) {
+        found_target_function = true;
+        break;
+      }
+    }
+    if (!found_target_function) {
+      std::stringstream strstr;
+      strstr << "Target function with id " << target_function
+             << " was requested, but not found in the module; stopping.";
+      spvtools::utils::CLIMessageConsumer(SPV_MSG_ERROR, nullptr, {},
+                                          strstr.str().c_str());
+      return 1;
+    }
+  }
+
   std::vector<uint32_t> binary_out;
   const auto reduction_status = reducer.Run(std::move(binary_in), &binary_out,
                                             reducer_options, validator_options);
diff --git a/tools/sva/yarn.lock b/tools/sva/yarn.lock
index 11ba12f..34a1808 100644
--- a/tools/sva/yarn.lock
+++ b/tools/sva/yarn.lock
@@ -938,9 +938,9 @@
     path-exists "^3.0.0"
 
 lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14:
-  version "4.17.15"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
-  integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+  version "4.17.19"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
+  integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
 
 log-symbols@2.2.0:
   version "2.2.0"
diff --git a/utils/vscode/src/parser/parser.go b/utils/vscode/src/parser/parser.go
index 1775b0f..260a616 100644
--- a/utils/vscode/src/parser/parser.go
+++ b/utils/vscode/src/parser/parser.go
@@ -356,7 +356,7 @@
 
 	lastPos := Position{}
 	for l.e == nil {
-		// Sanity check the parser is making progress
+		// Integrity check that the parser is making progress
 		if l.pos == lastPos {
 			log.Panicf("Parsing stuck at %v", l.pos)
 		}